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
我是以 HTTP 1.1 还是 HTTP 2 格式响应客户端?出现疑问是因为在我的客户端测试用例中我明确指定了版本(HTTP_2)但是请求没有升级headers,所以我是否应该以 HTTP 1.1 格式响应?
有没有办法让 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
完全不同,因为 Connection
和 Upgrade
是 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 和安全)客户端、代理和服务器。
所以,我正在管理公司的 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
我是以 HTTP 1.1 还是 HTTP 2 格式响应客户端?出现疑问是因为在我的客户端测试用例中我明确指定了版本(HTTP_2)但是请求没有升级headers,所以我是否应该以 HTTP 1.1 格式响应?
有没有办法让 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
完全不同,因为 Connection
和 Upgrade
是 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 和安全)客户端、代理和服务器。