Force immediate insecure HTTP2 connection with Java HttpClient
Asked Answered
A

1

6

Using only the standard Java library, as a client, how can an insecure HTTP/2 connection be established with prior knowledge that that version of the protocol will be used? I.e. without sending an upgrade request over HTTP/1.1 first.

I've tried using the utilities in java.net.http, calling version(HttpClient.Version.HTTP_2) on request and client builders, however an initial request is always sent over HTTP/1.1 with an upgrade header. So far, the only way to force version 2 from the beginning, seems to be to use a secure connection over https (which I would like to avoid).

I'd also like to stick to using only classes included in OpenJDK 11 (no netty or the like).

Agnella answered 26/9, 2018 at 9:6 Comment(1)
What's funny, such feature, if I understand correctly, is stated as a goal of the corresponding JEP 110: > Must be able to negotiate an upgrade from 1.1 to 2 (or not), or select 2 from the start.Dupont
H
5

I'm afraid this doesn't appear to be possible in the Java 11 http client API.

The most relevant internal JDK class is Http2Connection.java. It lists the three "creation" cases we'd expect for HTTP/2 connections:

  1. upgraded HTTP/1.1 plain tcp connection
  2. prior knowledge directly created plain tcp connection
  3. directly created HTTP/2 SSL connection which uses ALPN.

We're interested in case 2. There is code for this:

/**
 * Cases 2) 3)
 *
 * request is request to be sent.
 */
private Http2Connection(HttpRequestImpl request,
                        Http2ClientImpl h2client,
                        HttpConnection connection)
    throws IOException
{
    ...
}

Alas, this constructor is only actually used from a method for secure connections:

// Requires TLS handshake. So, is really async
static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,
                                                      Http2ClientImpl h2client,
                                                      Exchange<?> exchange) {
    assert request.secure();
    AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)
    ...
    return connection.connectAsync(exchange)
              .thenCompose(unused -> connection.finishConnect())
              .thenCompose(unused -> checkSSLConfig(connection))
              .thenCompose(notused-> {
                  CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
                  try {
                      Http2Connection hc = new Http2Connection(request, h2client, connection);
                      cf.complete(hc);
                  } catch (IOException e) {
                      cf.completeExceptionally(e);
                  }
                  return cf; } );

The non-TLS constructors do only seem to be called when upgrading a HTTP 1.1 connection.

I've looked into many of the other classes, too. I can't see anything that would suggest cleartext HTTP/2 connections can be negotiated without upgrade from HTTP 1.1 in the JDK 11 client.

What I find matches very much with the API description in the javadocs for HttpClient.Builder which only says this about choosing HTTP/2 as the version:

If set to HTTP/2, then each request will attempt to upgrade to HTTP/2. If the upgrade succeeds, then the response to this request will use HTTP/2 and all subsequent requests and responses to the same origin server will use HTTP/2. If the upgrade fails, then the response will be handled using HTTP/1.1

There is a lot of interesting stuff going on in the HTTP/2 connection reuse. For example, where concurrent requests are upgraded, only one is kept around on for future use in the HTTP/2 connection "cache". The cached connection will expire when the number of streams reaches the maximum, which is ~ 2^31-1, such that further requests won't exceed it, but existing streams will remain open. That's a lot of streams. I think that, if your use case is like mine -- a web service client in very long running applications -- then the cost of the upgrade on practically only the first request is negligable. I'd still like to eliminate it, mainly for simplicity, but I'm starting to think it isn't worth it.

Like the asker I'd like to stick with the JDK client. I will just mention that Apache HttpClient 5 beta appears to support forcing HTTP/2 (HttpVersionPolicy.FORCE_HTTP_2). I can't advise any further on it, because the beta doesn't have much documentation and comments are far sparser than the JDK implementation. But if I try it and have success, I will update my answer.

Hippocras answered 19/3, 2019 at 2:25 Comment(1)
Thanks for the analysis of the implementation! I had the same question, and I think performance is probably not the reason to use this — but an ability to establish an HTTP/2 connection in a local development environment (for testing, experimentation, benchmarking) without managing the keys and establishing a TLS connection, which also complicates packet analysis in, say, Wireshark. It would definitely be useful to have such a feature in the standard library.Dupont

© 2022 - 2024 — McMap. All rights reserved.