使用Java HttpClient强制立即建立不安全的HTTP2连接

5
仅使用标准Java库作为客户端,如何在“事先知道”将使用协议版本的情况下建立不安全的HTTP/2连接?即:在首先发送HTTP/1.1升级请求之前。我尝试使用java.net.http中的实用程序,在请求和客户端构建器上调用version(HttpClient.Version.HTTP_2),但始终会发送初始请求带有升级头的HTTP/1.1。到目前为止,强制从开始使用版本2的唯一方法似乎是使用https上的安全连接(我想避免这样做)。我还希望坚持仅使用OpenJDK 11中包含的类(无netty或类似内容)。

有趣的是,如果我理解正确的话,这个功能是在相应的 JEP 110 中作为一个目标来说明的:
必须能够从1.1升级到2(或不升级),或者从一开始就选择2
- Dmitry Timofeev
1个回答

4

恐怕在Java 11的http客户端API中似乎不可能实现。

最相关的JDK内部类是Http2Connection.java。 它列出了我们期望的HTTP / 2连接的三种“创建”情况:

  1. 升级的HTTP / 1.1普通tcp连接
  2. 直接创建的明文tcp连接(使用先前知识)
  3. 直接创建的使用ALPN的HTTP / 2 SSL连接。

我们感兴趣的是第2种情况。 这里有相应的代码:

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

唉,这个构造函数实际上只从安全连接的方法中使用:

// 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; } );

非 TLS 构造函数似乎仅在升级 HTTP 1.1 连接时才会被调用。
我也研究了许多其他类,但我看不到任何迹象表明JDK 11客户端可以通过 HTTP 1.1 升级以外的方法协商明文 HTTP/2 连接。
我的发现与 HttpClient.Builder 的 API 描述非常相似,它只是关于选择 HTTP/2 版本的说明如下:
如果设置为 HTTP/2,则每个请求都将尝试升级到 HTTP/2。如果升级成功,则对此请求的响应将使用 HTTP/2,并且所有后续请求和相应都将使用 HTTP/2。如果升级失败,则响应将使用 HTTP/1.1 处理。
HTTP/2连接复用中有很多有趣的事情。例如,在并发请求升级时,只有一个请求被保留在HTTP/2连接“缓存”中以备将来使用。当流的数量达到最大值(约为2^31-1)时,缓存的连接将过期,以便进一步的请求不会超出该限制,但现有的流将保持打开状态。这是很多流。如果你的用例和我一样——在非常长时间运行的应用程序中作为Web服务客户端——那么升级的成本实际上仅针对第一个请求是微不足道的。我仍然想消除它,主要是为了简单起见,但我开始觉得这并不值得。
像提问者一样,我想坚持使用JDK客户端。我只想提一下,Apache HttpClient 5 beta似乎支持强制使用HTTP/2(HttpVersionPolicy.FORCE_HTTP_2)。我无法进一步提供建议,因为beta版本没有太多文档,注释也比JDK实现稀少得多。但如果我尝试并成功了,我会更新我的答案。

3
感谢对实现的分析!我有同样的问题,我认为性能可能不是使用这个功能的原因,而是在本地开发环境中建立HTTP/2连接的能力(用于测试、实验和基准测试),而无需管理密钥和建立TLS连接,这也使得在例如Wireshark中进行数据包分析更加复杂。在标准库中拥有这样的功能肯定会非常有用。 - Dmitry Timofeev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接