HttpClient:通过代理发送的 http 2 请求没有升级 headers

HttpClient : http 2 request sent via proxy has no upgrade headers

所以,我正在管理公司的 http 服务器,并被要求升级服务器以支持使用 http 2 协议的代理请求

这是测试客户端。我在 jdk 17 中使用 HttpClient。这是测试用例。

    public static void main(String[] args)throws Exception
     {
      HttpClient client=HttpClient.newBuilder()
                                  .proxy(ProxySelector.of(new InetSocketAddress("192.168.1.2",1000))) //proxy to 192.168.1.2:1000 
                                  .version(HttpClient.Version.HTTP_2)    //use http 2                      
                                  .build();
      
      //request to 192.168.1.2:2000
      HttpRequest request=HttpRequest.newBuilder(URI.create("http://192.168.1.2:2000/Test.txt"))
                                      .build();
      
      HttpResponse response=client.send(request,BodyHandlers.ofString());
      System.out.println("Status code: " + response.statusCode());                            
      System.out.println("Headers: " + response.headers().map());
      System.out.println("Body: " + response.body());
     } 

我的服务器 运行 使用 http 2 协议,我没有收到任何响应。调试后,我的服务器收到的数据包如下所示:

    GET http://192.168.1.2:2000/Test.txt HTTP/1.1
    Content-Length: 0
    Host: 192.168.1.2:2000
    User-Agent: Java-http-client/17.0.2

这既不是 http 2 格式,也没有我的服务器设计用于解析的任何升级 headers,在不使用代理的情况下通常看起来像这样:

    GET /Test.txt HTTP/1.1
    Connection: Upgrade, HTTP2-Settings
    Content-Length: 0
    Host: 192.168.1.2:2000
    HTTP2-Settings: AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA
    Upgrade: h2c
    User-Agent: Java-http-client/17.0.2

我的服务器旨在解析此请求,因为它 upgrade request 包含客户端的 http 2 设置。

所以,当我收到这样的请求时,我的问题是:

     GET http://192.168.1.2:2000/Test.txt HTTP/1.1
     Content-Length: 0
     Host: 192.168.1.2:2000
     User-Agent: Java-http-client/17.0.2
  1. 我是以 HTTP 1.1 还是 HTTP 2 格式响应客户端?出现疑问是因为在我的客户端测试用例中我明确指定了版本(HTTP_2)但是请求没有升级headers,所以我是否应该以 HTTP 1.1 格式响应?

  2. 有没有办法让 HttpClient 包含升级 headers 以便我可以以 HTTP 2 格式回复?

编辑。

为了调试响应,我在端口 1000 上创建了一个简单的 TCP 服务器 运行 并通过它进行代理。我简单地打印在这个代理上接收到的字节并得到完全相同的输出

//Test Proxy server running on port 1000. No parsing data simply print and close for debugging purposes
public static void main(String[] args)throws Exception
 {
  try(ServerSocket server=new ServerSocket(1000))
  {
   try(Socket client=server.accept())
   {
    try(InputStream input=client.getInputStream())
    {
     byte[] data=new byte[8196];
     int length=input.read(data);
     System.out.println(new String(data,0,length));
    } 
   } 
  } 
 }

显然,OpenJDK 的 HttpClient 只能向代理发送 HTTP/1.1 请求。

即使你指定了HttpClient.Version.HTTP_2,因为有代理,OpenJDK的HttpClient会忽略你指定的版本,将请求格式化为HTTP/1.1并发送给代理.

OpenJDK 的 HttpClient 代理功能非常有限(例如,您无法轻松使用安全协议与代理通信),因此您可能需要研究不同的 HTTP 客户端。

[免责声明,我是 Jetty 团队的一员。]

Jetty 的 HttpClient 支持更灵活的代理配置,因此您可以指定代理使用的协议(以及它是否安全)。

注意需要代理配合才能得到你想要的,因为Connection是一个hop-by-hop header,所以只在单跳有效,即从客户端到代理。

代理可以合法的把客户端的HTTP/1.1升级到HTTP/2的请求,然后用HTTP/1.1跟服务器对话,得到[=64= .1 从服务器返回响应并产生一个 101 Upgrade 响应,然后是一个 HTTP/2 响应转换为 HTTP/2 服务器的 HTTP/1.1 响应:

client                    proxy               server
   | ----  h1+upgrade  ---> |                    |
   |                        | --  h1 request --> |
   |                        | <-- h1 response -- |
   | <-- h1 101 upgrade  -- |
   | <--- h2c response  --- |

支持 h2c 的代理可以改为执行您想要的操作:

client                     proxy                    server
   | ----  h1+upgradeA  ---> |                        |
   |                         | ---  h1+upgradeB  ---> |
   |                         | <--  h1 101 upgrade -- |
   |                         | <--- h2c response ---- |
   | <--  h1 101 upgrade  -- |
   | <--- h2c response  ---- |

但是请注意 upgradeA 可能与 upgradeB 完全不同,因为 ConnectionUpgrade 是 hop-by-hop header,并且代理相对于客户端可能有不同的 HTTP/2 配置(因此也会发送不同的 HTTP2-Settings header)。

如果client和proxy之间的通信是clear-text,你必须事先知道proxy是否支持h2c,否则client会默认为HTTP/1.1 (就像 OpenJDK 的 HttpClient 一样)。 相比之下,Jetty 的 HttpClient 可以配置你想用来与代理对话的协议。

或者,您必须通过 TLS 使用安全通信,以便客户端和代理可以协商他们想要使用的协议(例如 h2)。

代理同理:如果与服务器的通信是clear-text(你的情况),代理必须事先知道服务器支持h2c,所以你需要配置具有该信息的代理,否则代理可能会默认为 HTTP/1.1.

显然代理必须同时支持 HTTP/1.1 和 HTTP/2.

如果您正在实施代理,将 Connection(和其他 hop-by-hop headers)复制到服务器在技术上是错误的,但它可能在受限、更简单、个案。 请参阅 this section of RFC 7230,其中指定了代理应如何删除 hop-by-hop header。

FTR,this test 表明 Jetty(客户端和服务器)可以支持协议之间的任意组合(HTTP/1.1 和 HTTP/2,clear-text 和安全)客户端、代理和服务器。