Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support making HTTP client requests using HTTP/2 #484

Open
pjfanning opened this issue Feb 14, 2024 · 8 comments
Open

support making HTTP client requests using HTTP/2 #484

pjfanning opened this issue Feb 14, 2024 · 8 comments
Labels
enhancement New feature or request

Comments

@pjfanning
Copy link
Contributor

pjfanning commented Feb 14, 2024

See matsluni/aws-spi-akka-http#226 (comment) for context.

It appears that Http().singleRequest(...) only supports sending HTTP/1.1 requests.

We have samples that use HTTP/2 requests but it might be nice to make it more straightforward.

@pjfanning pjfanning added the enhancement New feature or request label Feb 14, 2024
@samueleresca
Copy link
Member

Is this issue available for being picked up? I should have time in the next few weeks.

@pjfanning
Copy link
Contributor Author

Is this issue available for being picked up? I should have time in the next few weeks.

Sure - this is open for anyone to pick it up

@samueleresca
Copy link
Member

I was taking a look at this. Http().singleRequest(...) a.k.a Request-Level Client-Side API uses a connection pool (see: PoolInterface) to reuse connections. In order to expose an single request Http2 API(i.e.:Http2().singleRequest(HttpRequest(uri = "http://localhost"))) and keeping the same behavior of Http().singleRequest I think we can proceed as follow:

Is my understanding / approach right here? Tagging explicitly @raboof as I saw his name in some of the files involved in the changes + some HTTP/2 related work.

@jrudolph
Copy link
Contributor

A few thoughts here:

  • One main point of HTTP/2 is that you only need a single TCP connection open and multiplex multiple requests on top of it. The benefit is to amortize the cost of setting up a connection (TLS and CWND setup). One drawback is that having a single connection is a potential clump risk, i.e. it may stay open longer than intended (load balancing changes), any TCP connection state problems will affect all ongoing (and potentially future) requests.
  • The main point of the HTTP/1.1 singleRequest / pool API is to relieve the user of the HTTP connection handling. With HTTP/2 this is less of a concern as a single connection can already support multiple ongoing HTTP requests at the same time.

What we already have for HTTP/2:

  • create a single connection and multiplex requests over it using OutgoingConnection.http2 (returns a Flow)
  • create a "managed connection" which automatically reconnects an HTTP2 connection when the connection fails using OutgoingConnection.managedPersistentHttp2 (returns a Flow)

What is missing:

  • "pool" for HTTP/2 that keeps a single managed persistent HTTP/2 connection per host and automatically dispatches requests based on the host to that connection (that relieves code using the client from keeping state)
  • singleRequest-like APIs using that pool (or on top of one of the existing Flow returning solutions)
  • any kind of automatic negotiation of whether to use HTTP/1.1 or HTTP/2

In terms of essential functionality, it seems that not much is missing. Many backend usages that need an HTTP/2 client are already supported if you can do the "dispatch to the right host connection" (which only requires that you can keep the single managed connection as a state in your client shim).

@jrudolph
Copy link
Contributor

I think we can proceed as follow

I don't think we should do it like this. Especially, we don't want another entry point like Http2(). If we want to allow HTTP2 to be integrated more deeply, it should be done transparently using the existing APIs.

In theory, all existing APIs can be supported by "just" allowing PoolInterface to deal with HTTP/2. This might not be a trivial task because of assumptions between the PoolInterfaceStage and NewHostConnectionPool. Let's say, we ignore automatic negotiation for now and require that you need to configure whether you want to connect to a host via HTTP/1.1 or HTTP/2 (i.e. a new setting in host-connection-pool`).

What needs to be done when HTTP/2 is selected is to replace the code at PoolInterface.apply, to create an alternative connection flow that uses a single managed persistent HTTP/2 connection instead of the pool of HTTP/1.1 connections.

val connectionFlow =
Http().outgoingConnectionUsingContext(host, port, connectionContext, settings.connectionSettings, setup.log)
val poolFlow = NewHostConnectionPool(connectionFlow, settings, log).named("PoolFlow")

On the surface, it might be enough to switch out those two lines with the HTTP/2 equivalent. I wouldn't surprised if there are some subtle issues/bugs remaining in managedPersistentHttp2 which was a late addition to HTTP/2 support in akka-http.

Some notes on automatic negotiation:

You can do protocol negotiation, either by being told that HTTP/2 protocol is available using the alt-svc HTTP header, or by participating in TLS / ALPN negotiation. In any case, you only get told that HTTP/2 is available relatively late in the connection setup. Since the flows for HTTP/1.1 and HTTP/2 need to be wired differently, it is hard to negotiate on-the-fly and then wire things correctly in a pool setup. That means, you need to run a pre-flight request to figure out whether a certain host would support HTTP/2 and keep that information around for the rest of the session / application runtime. I would see that as an additional future feature. We could keep the http2 selection setting in host-connection-pool as selecting from http1 / http2 and then later add auto or preflight.

@jrudolph
Copy link
Contributor

What needs to be done when HTTP/2 is selected is to replace the code at PoolInterface.apply, to create an alternative connection flow that uses a single managed persistent HTTP/2 connection instead of the pool of HTTP/1.1 connections.

@samueleresca this is the first thing I would try. Hopefully, the flows implemented by OutgoingConnection.managedPersistentHttp2 and NewHostConnectionPool are compatible enough for it to work.

@jrudolph
Copy link
Contributor

OutgoingConnection.managedPersistentHttp2 and NewHostConnectionPool are compatible enough for it to work

Which is not quite the case right now. NewHostConnectionPool is Flow[RequestContext, ResponseContext] while managedPersistentHttp2 is Flow[HttpRequest, HttpResponse]. That's probably not a blocker, it's more an artifact of the HTTP/1.1 low-level impl not (yet) supporting the more general RequestResponseAssociation protocol to associated requests with responses that HTTP/2 supports.

@samueleresca
Copy link
Member

@jrudolph Thanks for the steering.

I don't think we should do it like this. Especially, we don't want another entry point like Http2(). If we want to allow HTTP2 to be integrated more deeply, it should be done transparently using the existing APIs.

ACK on the above. So the apporach you are suggesting is to skip the initialization of the NewHostConnectionPool in case http2 is specified and to use directly the flow coming from managedPersistentHttp2 for constructing the PoolInterface?

Which is not quite the case right now. NewHostConnectionPool is Flow[RequestContext, ResponseContext] while managedPersistentHttp2 is Flow[HttpRequest, HttpResponse]. That's probably not a blocker, it's more an artifact of the HTTP/1.1 low-level impl not (yet) supporting the more general RequestResponseAssociation protocol to associated requests with responses that HTTP/2 supports.

What is your recommendation here? I'm seeing that [Request|Response]Context is a case class wrapping a Http[Request|Response]. Should I create a new PoolInterfaceStage implementation that handle Flow[HttpRequest, HttpResponse] instead of Flow[RequestContext, ResponseContext]?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants