Reputation: 2682
I am working with Akka HTTP, and I have a client that queries a URL. The code is something like this:
def f1(): Future[HttpResponse] = { ... }
I want to write a f2
function that takes the HttpRespons
e but turns it into a Future[String]
where the String
is the body of the message. So I am looking for:
def f2(): Future[String] = f1().map(...)
I am unable to convert the get this to work correctly.
Question #1: This is more of a basic Scala question, I think. How would I convert this future? The best I can get is:
def f2:Future[String] = {
f1().map (_.entity.toStrict(300.milli).map(_.data.utf8String))
}
which doesn't work since I end up with a Future[Future[String]]
Question #2: This is an akka-http question for my understanding.
does f1().map(_.toStrict())
imply f1().map(_.entity.toStrict())
?
If so, how can I access the entity without having to call toStrict on it as well?
Upvotes: 3
Views: 5140
Reputation: 1562
For Q1:
If you get Future[Future[_]]
where you need Future[_]
, you should replace map
with flatMap
. The same applies to Option
and collections.
So this looks correct:
def f1(): Future[HttpResponse]
def f2(): Future[String] = {
f1().flatMap (_.entity.toStrict(300.milli).map(_.data.utf8String))
}
For Q2:
HttpMessage.toStrict
does call HttpEntity.toStrict
internally so they do the same thing. (Note that for some reason HttpMessage.toStrict
is not exposed in Java interface).
Upvotes: 2
Reputation: 118
// 1. You can use a for comprehension (with Option[Option[T]], Future[Future[T]], Try[Try[T]) or flatMap or .map { _ pipeTo self } (if in an actor).
val httpResponseF: Future[HttpResponse] = Future.successful(HttpResponse(entity = HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)`, data = ByteString("test"))))
val res: Future[String] = for {
httpResponse <- httpResponseF
entity <- httpResponse.entity.toStrict(300.milli)
} yield {
entity match {
case HttpEntity.Strict(contentType, data) => data.utf8String
}
}
assert(Await.result(res, 1.second) == "test")
// 2. They look different to me: akka.http.scaladsl.model.HttpResponse
def
toStrict(timeout: FiniteDuration)(implicit ec: ExecutionContext, fm: Materializer): Future[Self]
vs
//akka.http.javadsl.model.ResponseEntity
def
toStrict(timeoutMillis: Long, materializer: Materializer): CompletionStage[HttpEntity.Strict]
One uses implicits.
Here is one way to get a string as an http client.
object Example {
import scala.concurrent.Future
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import akka.stream.scaladsl.Source
import akka.http.scaladsl.unmarshalling._
import akka.http.scaladsl.model._
import scala.util.{Try,Success,Failure}
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
// construct a pool client flow with context type `Int`
val poolClientFlow = Http().cachedHostConnectionPool[Int]("akka.io")
val responseFuture: Future[String] =
Source.single(HttpRequest(uri = "/") -> 1)
.via(poolClientFlow)
.mapAsync(1) {
case (Success(response), i) => Unmarshal(response.entity).to[String]
case (Failure(e), i) => Future.failed(e)
}
.runWith(Sink.head)
}
If there is a more specific type (more idiomatic if using Scala) you are trying to unmarshall to rather than string you can create a custom Unmarshaller using this Json one as a template: https://github.com/hseeberger/akka-http-json/blob/master/akka-http-json4s/src/main/scala/de/heikoseeberger/akkahttpjson4s/Json4sSupport.scala otherwise there is Unmarshallers.String http://doc.akka.io/docs/akka/2.4.3/java/http/routing-dsl/marshalling.html#Unmarshalling
Sometimes checking the Spray mailing list and docs helps since it has been active longer even though there is a new way to do it in akka-http it at least provides a starting point.
Upvotes: 3