• No results found

Consuming HTTP-based Services (Client-Side)

All client-side functionality of Akka HTTP, for consuming HTTP-based services offered by other endpoints, is currently provided by theakka-http-coremodule.

Depending on your application’s specific needs you can choose from three different API levels:

Connection-Level Client-Side API for full-control over when HTTP connections are opened/closed and how re-

quests are scheduled across them

Host-Level Client-Side API for letting Akka HTTP manage a connection-pool toone specifichost/port endpoint

Request-Level Client-Side API for letting Akka HTTP perform all connection management

You can interact with different API levels at the same time and, independently of which API level you choose, Akka HTTP will happily handle many thousand concurrent connections to a single or many different hosts.

2.6.1 Connection-Level Client-Side API

The connection-level API is the lowest-level client-side API Akka HTTP provides. It gives you full control over when HTTP connections are opened and closed and how requests are to be send across which connection. As such it offers the highest flexibility at the cost of providing the least convenience.

Opening HTTP Connections

With the connection-level API you open a new HTTP connection to a target endpoint by materializing aFlow

returned by theHttp.get(system).outgoingConnection(...)method. Here is an example: final ActorSystem system = ActorSystem.create();

final ActorMaterializer materializer = ActorMaterializer.create(system);

final Flow<HttpRequest, HttpResponse, Future<OutgoingConnection>> connectionFlow = Http.get(system).outgoingConnection("akka.io", 80);

final Future<HttpResponse> responseFuture = Source.single(HttpRequest.create("/"))

.via(connectionFlow)

.runWith(Sink.<HttpResponse>head(), materializer);

Apart from the host name and port theHttp.get(system).outgoingConnection(...) method also allows you to specify socket options and a number of configuration settings for the connection.

Note that no connection is attempted until the returned flow is actually materialized! If the flow is materialized several times then several independent connections will be opened (one per materialization). If the connection at- tempt fails, for whatever reason, the materialized flow will be immediately terminated with a respective exception.

Request-Response Cycle

Once the connection flow has been materialized it is ready to consumeHttpRequestinstances from the source it is attached to. Each request is sent across the connection and incoming responses dispatched to the downstream pipeline. Of course and as always, back-pressure is adequately maintained across all parts of the connection. This means that, if the downstream pipeline consuming the HTTP responses is slow, the request source will eventually be slowed down in sending requests.

Any errors occurring on the underlying connection are surfaced as exceptions terminating the response stream (and canceling the request source).

Note that, if the source produces subsequent requests before the prior responses have arrived, these requests will bepipelinedacross the connection, which is something that is not supported by all HTTP servers. Also, if the server closes the connection before responses to all requests have been received this will result in the response stream being terminated with a truncation error.

Closing Connections

Akka HTTP actively closes an established connection upon reception of a response containingConnection: closeheader. The connection can also be closed by the server.

An application can actively trigger the closing of the connection by completing the request stream. In this case the underlying TCP connection will be closed when the last pending response has been received.

Timeouts

Currently Akka HTTP doesn’t implement client-side request timeout checking itself as this functionality can be regarded as a more general purpose streaming infrastructure feature. However, akka-stream should soon provide such a feature.

Stand-Alone HTTP Layer Usage // TODO

2.6.2 Host-Level Client-Side API

As opposed to the connection-level-api the host-level API relieves you from manually managing individual HTTP connections. It autonomously manages a configurable pool of connections toone particular target endpoint(i.e. host/port combination).

Requesting a Host Connection Pool

The best way to get a hold of a connection pool to a given target endpoint is the

Http.get(system).cachedHostConnectionPool(...) method, which returns a Flow that can be “baked” into an application-level stream setup. This flow is also called a “pool client flow”.

The connection pool underlying a pool client flow is cached. For everyActorSystem, target endpoint and pool configuration there will never be more than a single pool live at any time.

Also, the HTTP layer transparently manages idle shutdown and restarting of connection pools as configured. The client flow instances therefore remain valid throughout the lifetime of the application, i.e. they can be materialized as often as required and the time between individual materialization is of no importance.

When you request a pool client flow withHttp.get(system).cachedHostConnectionPool(...)

Akka HTTP will immediately start the pool, even before the first client flow materialization. However, this running pool will not actually open the first connection to the target endpoint until the first request has arrived.

Configuring a Host Connection Pool

Apart from the connection-level config settings and socket options there are a number of settings that allow you to influence the behavior of the connection pool logic itself. Check out the

akka.http.client.host-connection-poolsection of the Akka HTTPConfigurationfor more infor- mation about which settings are available and what they mean.

Note that, if you request pools with different configurations for the same target host you will getindependent pools. This means that, in total, your application might open more concurrent HTTP connections to the target endpoint than any of the individual pool’smax-connectionssettings allow!

There is one setting that likely deserves a bit deeper explanation: max-open-requests. This setting limits the maximum number of requests that can be in-flight at any time for a single connection pool. If an application callsHttp.get(system).cachedHostConnectionPool(...) 3 times (with the same endpoint and settings) it will get back3different client flow instances for the same pool. If each of these client flows is then materialized4times (concurrently) the application will have 12 concurrently running client flow materializations. All of these share the resources of the single pool.

This means that, if the pool’s pipelining-limit is left at1 (effecitvely disabeling pipelining), no more than 12 requests can be open at any time. With a pipelining-limit of8 and 12 concurrent client flow materializations the theoretical open requests maximum is96.

Themax-open-requestsconfig setting allows for applying a hard limit which serves mainly as a protection against erroneous connection pool use, e.g. because the application is materializing too many client flows that all compete for the same pooled connections.

Using a Host Connection Pool

The “pool client flow” returned byHttp.get(system).cachedHostConnectionPool(...) has the following type:

// TODO Tuple2 will be changed to be `akka.japi.Pair`

Flow[Tuple2[HttpRequest, T], Tuple2[Try[HttpResponse], T], HostConnectionPool]

This means it consumes tuples of type (HttpRequest, T) and produces tuples of type

(Try[HttpResponse], T) which might appear more complicated than necessary on first sight. The reason why the pool API includes objects of custom type T on both ends lies in the fact that the underlying transport usually comprises more than a single connection and as such the pool client flow often generates responses in an order that doesn’t directly match the consumed requests. We could have built the pool logic in a way that reorders responses according to their requests before dispatching them to the application, but this would have meant that a single slow response could block the delivery of potentially many responses that would otherwise be ready for consumption by the application.

In order to prevent unnecessary head-of-line blocking the pool client-flow is allowed to dispatch responses as soon as they arrive, independently of the request order. Of course this means that there needs to be another way to associate a response with its respective request. The way that this is done is by allowing the application to pass along a custom “context” object with the request, which is then passed back to the application with the respective response. This context object of typeTis completely opaque to Akka HTTP, i.e. you can pick whatever works best for your particular application scenario.

Connection Allocation Logic

This is how Akka HTTP allocates incoming requests to the available connection “slots”:

1. If there is a connection alive and currently idle then schedule the request across this connection. 2. If no connection is idle and there is still an unconnected slot then establish a new connection.

3. If all connections are already established and “loaded” with other requests then pick the connection with the least open requests (< the configuredpipelining-limit) that only has requests with idempotent methods scheduled to it, if there is one.

4. Otherwise apply back-pressure to the request source, i.e. stop accepting new requests.

For more information about scheduling more than one request at a time across a single connection see this wikipedia entry on HTTP pipelining.

Retrying a Request

If themax-retriespool config setting is greater than zero the pool retries idempotent requests for which a response could not be successfully retrieved. Idempotent requests are those whose HTTP method is defined to be idempotent by the HTTP spec, which are all the ones currently modelled by Akka HTTP except for thePOST,

PATCHandCONNECTmethods.

When a response could not be received for a certain request there are essentially three possible error scenarios: 1. The request got lost on the way to the server.

2. The server experiences a problem while processing the request. 3. The response from the server got lost on the way back.

Since the host connector cannot know which one of these possible reasons caused the problem and therefore

PATCHandPOSTrequests could have already triggered a non-idempotent action on the server these requests cannot be retried.

In these cases, as well as when all retries have not yielded a proper response, the pool produces a failedTry(i.e. ascala.util.Failure) together with the custom request context.

Pool Shutdown

Completing a pool client flow will simply detach the flow from the pool. The connection pool itself will con- tinue to run as it may be serving other client flows concurrently or in the future. Only after the configured

idle-timeoutfor the pool has expired will Akka HTTP automatically terminate the pool and free all its re- sources.

If a new client flow is requested withHttp.get(system).cachedHostConnectionPool(...) or if an already existing client flow is re-materialized the respective pool is automatically and transparently restarted. In addition to the automatic shutdown via the configured idle timeouts it’s also possible to trigger the immediate shutdown of a specific pool by calling shutdown()on the HostConnectionPoolinstance that the pool client flow materializes into. Thisshutdown()call produces a Future[Unit]which is fulfilled when the pool termination has been completed.

It’s also possible to trigger the immediate termination of all connection pools in the ActorSystem at the same time by callingHttp.get(system).shutdownAllConnectionPools(). This call too produces aFuture[Unit]which is fulfilled when all pools have terminated.

Example

final ActorSystem system = ActorSystem.create();

final ActorMaterializer materializer = ActorMaterializer.create(system);

// construct a pool client flow with context type `Int` // TODO these Tuple2 will be changed to akka.japi.Pair final Flow<

Tuple2<HttpRequest, Integer>, Tuple2<Try<HttpResponse>, Integer>, HostConnectionPool> poolClientFlow =

Http.get(system).<Integer>cachedHostConnectionPool("akka.io", 80, materializer);

// construct a pool client flow with context type `Int`

Source

.single(Pair.create(HttpRequest.create("/"), 42).toScala()) .via(poolClientFlow)

.runWith(Sink.<Tuple2<Try<HttpResponse>, Integer>>head(), materializer);

2.6.3 Request-Level Client-Side API

The request-level API is the most convenient way of using Akka HTTP’s client-side functionality. It internally builds upon theHost-Level Client-Side APIto 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.

Flow-Based Variant

The flow-based variant of the request-level client-side API is presented by the Http().superPool(...)

method. It creates a new “super connection pool flow”, which routes incoming requests to a (cached) host con- nection pool depending on their respective effective URIs.

TheFlowreturned byHttp().superPool(...) is very similar to the one from theHost-Level Client-Side API, so theUsing a Host Connection Poolsection 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 validHostheader. The host connection pool will automatically add aHost

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

Hostheader, because otherwise it’d be impossible to find out which target endpoint to direct the request to. Future-Based Variant

Sometimes 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(...) method, which simply turns an

HttpRequest instance into Future<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

Hostheader, otherwise the returned future will be completed with an error. Example

final ActorSystem system = ActorSystem.create();

final ActorMaterializer materializer = ActorMaterializer.create(system);

final Future<HttpResponse> responseFuture = Http.get(system)

.singleRequest(HttpRequest.create("http://akka.io"), materializer);

2.6.4 Client-Side HTTPS Support

Akka HTTP supports TLS encryption on the client-side as well as on theserver-side.

The central vehicle for configuring encryption is the HttpsContext, which can be created using the static methodHttpsContext.createwhich is defined like this:

public static HttpsContext create(SSLContext sslContext,

Option<Collection<String>> enabledCipherSuites, Option<Collection<String>> enabledProtocols, Option<ClientAuth> clientAuth,

Option<SSLParameters> sslParameters)

In addition to the outgoingConnection, newHostConnectionPool and

cachedHostConnectionPool methods the akka.http.javadsl.Http extension also defines outgoingConnectionTls, newHostConnectionPoolTls and

cachedHostConnectionPoolTls. These methods work identically to their counterparts without the

-Tlssuffix, with the exception that all connections will always be encrypted.

ThesingleRequestandsuperPoolmethods determine the encryption state via the scheme of the incoming request, i.e. requests to an “https” URI will be encrypted, while requests to an “http” URI won’t.

The encryption configuration for all HTTPS connections, i.e. theHttpsContextis determined according to the following logic:

1. If the optionalhttpsContextmethod parameter is defined it contains the configuration to be used (and thus takes precedence over any potentially set default client-sideHttpsContext).

2. If the optionalhttpsContextmethod parameter is undefined (which is the default) the default client-side

HttpsContextis used, which can be set via thesetDefaultClientHttpsContexton theHttp

extension.

3. If no default client-sideHttpsContexthas been set via thesetDefaultClientHttpsContexton theHttpextension the default system configuration is used.

Usually the process is, if the default system TLS configuration is not good enough for your application’s needs, that you configure a custom HttpsContext instance and set it via

Http.get(system).setDefaultClientHttpsContext. Afterwards you simply use

outgoingConnectionTls, newHostConnectionPoolTls, cachedHostConnectionPoolTls,

superPool or singleRequest without a specific httpsContext argument, which causes encrypted connections to rely on the configured default client-sideHttpsContext.

2.6.5 Client-Side WebSocket Support

Not yet implemented see17275.