Request-Level Client-Side API
The request-level API is the recommended and most convenient way of using Akka HTTP’s client-side functionality. It internally builds upon the Host-Level Client-Side API to provide you with a simple and easy-to-use way of retrieving HTTP responses from remote servers. Depending on your preference you can pick the flow-based or the future-based variant.
It is recommended to first read the Implications of the streaming nature of Request/Response Entities section, as it explains the underlying full-stack streaming concepts, which may be unexpected when coming from a background with non-“streaming first” HTTP Clients.
The request-level API is implemented on top of a connection pool that is shared inside the actor system. A consequence of using a pool is that long-running requests block a connection while running and starve other requests. Make sure not to use the request-level API for long-running requests like long-polling GET requests. Use the Connection-Level Client-Side API or an extra pool just for the long-running connection instead.
Future-Based Variant
Most often, your HTTP client needs are very basic. You simply need the HTTP response for a certain request and don’t want to bother with setting up a full-blown streaming infrastructure.
For these cases Akka HTTP offers the Http().singleRequest(...)
Http.get(system).singleRequest(...)
method, which simply turns an HttpRequestHttpRequest instance into Future[HttpResponse]
CompletionStage<HttpResponse>
. Internally the request is dispatched across the (cached) host connection pool for the request’s effective URI.
Just like in the case of the super-pool flow described above the request must have either an absolute URI or a valid Host
header, otherwise the returned future will be completed with an error.
Example
- Scala
-
import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model._ import akka.stream.ActorMaterializer import scala.concurrent.Future import scala.util.{ Failure, Success } object Client { def main(args: Array[String]): Unit = { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() // needed for the future flatMap/onComplete in the end implicit val executionContext = system.dispatcher val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "https://akka.io")) responseFuture .onComplete { case Success(res) => println(res) case Failure(_) => sys.error("something wrong") } } }
- Java
-
final ActorSystem system = ActorSystem.create(); final CompletionStage<HttpResponse> responseFuture = Http.get(system) .singleRequest(HttpRequest.create("https://akka.io"));
Using the Future-Based API in Actors
When using the Future
CompletionStage
based API from inside an ActorActor, all the usual caveats apply to how one should deal with the futures completion. For example you should not access the actor’s state from within the Future
CompletionStage
’s callbacks (such as map
, onComplete
, …) and instead you should use the pipeTo
pipe
pattern to pipe the result back to the actor as a message:
- Scala
-
import akka.actor.{ Actor, ActorLogging } import akka.http.scaladsl.Http import akka.http.scaladsl.model._ import akka.stream.{ ActorMaterializer, ActorMaterializerSettings } import akka.util.ByteString class Myself extends Actor with ActorLogging { import akka.pattern.pipe import context.dispatcher final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system)) val http = Http(context.system) override def preStart() = { http.singleRequest(HttpRequest(uri = "https://akka.io")) .pipeTo(self) } def receive = { case HttpResponse(StatusCodes.OK, headers, entity, _) => entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body => log.info("Got response, body: " + body.utf8String) } case resp @ HttpResponse(code, _, _, _) => log.info("Request failed, response code: " + code) resp.discardEntityBytes() } }
- Java
-
import akka.actor.AbstractActor; import akka.http.javadsl.Http; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.model.HttpResponse; import akka.japi.pf.ReceiveBuilder; import akka.stream.ActorMaterializer; import akka.stream.Materializer; import scala.concurrent.ExecutionContextExecutor; import java.util.concurrent.CompletionStage; import static akka.pattern.PatternsCS.pipe; public class SingleRequestInActorExample extends AbstractActor { final Http http = Http.get(context().system()); final ExecutionContextExecutor dispatcher = context().dispatcher(); final Materializer materializer = ActorMaterializer.create(context()); public SingleRequestInActorExample() { // syntax changes slightly in Akka 2.5, see the migration guide receive(ReceiveBuilder .match(String.class, url -> pipe(fetch(url), dispatcher).to(self())) .build()); } CompletionStage<HttpResponse> fetch(String url) { return http.singleRequest(HttpRequest.create(url), materializer); } }
Always make sure you consume the response entity streams (of type Source<ByteString,Unit>Source[ByteString,Unit]Source<ByteString, Object>Source[ByteString, Object]) by for example connecting it to a SinkSink]@java[
response.discardEntityBytes(Materializer)`] if you don’t care about the response entity), since otherwise Akka HTTP (and the underlying Streams infrastructure) will understand the lack of entity consumption as a back-pressure signal and stop reading from the underlying TCP connection!
This is a feature of Akka HTTP that allows consuming entities (and pulling them through the network) in a streaming fashion, and only on demand when the client is ready to consume the bytes - it may be a bit surprising at first though.
There are tickets open about automatically dropping entities if not consumed (#183 and #117), so these may be implemented in the near future.
Flow-Based Variant
The flow-based variant of the request-level client-side API is presented by the Http().superPool(...)
Http.get(system).superPool(...)
method. It creates a new “super connection pool flow”, which routes incoming requests to a (cached) host connection pool depending on their respective effective URIs.
The FlowFlow returned by Http().superPool(...)
Http.get(system).superPool(...)
is very similar to the one from the Host-Level Client-Side API, so the Using a Host Connection Pool section also applies here.
However, there is one notable difference between a “host connection pool client flow” for the host-level API and a “super-pool flow”: Since in the former case the flow has an implicit target host context the requests it takes don’t need to have absolute URIs or a valid Host
header. The host connection pool will automatically add a Host
header if required.
For a super-pool flow this is not the case. All requests to a super-pool must either have an absolute URI or a valid Host
header, because otherwise it’d be impossible to find out which target endpoint to direct the request to.
Collecting headers from a server response
Sometimes we would like to get only headers of specific type which are sent from a server. In order to collect headers in a type safe way Akka HTTP API provides a type for each HTTP header. Here is an example for getting all cookies set by a server (Set-Cookie
header):
- Scala
-
import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model.headers.{ HttpCookie, `Set-Cookie` } import akka.http.scaladsl.model._ import akka.stream.ActorMaterializer import scala.concurrent.Future object Client { def main(args: Array[String]): Unit = { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() implicit val executionContext = system.dispatcher val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "https://akka.io")) responseFuture.map { case HttpResponse(StatusCodes.OK, headers, entity, _) => val setCookies: Seq[HttpCookie] = headers.collect { case `Set-Cookie`(x) ⇒ x } println(s"Cookies set by a server: $setCookies") entity.discardBytes() case _ => sys.error("something wrong") } } }
- Java
-
import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import akka.http.javadsl.model.headers.SetCookie; final HttpResponse response = responseFromSomewhere(); List<SetCookie> setCookies = StreamSupport.stream(response.getHeaders().spliterator(), false) .filter(SetCookie.class::isInstance) .map(SetCookie.class::cast) .collect(Collectors.toList()); System.out.println("Cookies set by a server: " + setCookies); response.discardEntityBytes(materializer);