So, I am managing a company http server and have been asked to upgrade the server to support proxy requests using http 2 protocol
This is the test client. I am using HttpClient in jdk 17. Here is the test case.
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());
}
My server is running on http 2 protocol and I wasn't getting any response. Upon debugging the packet received on my server looked like this:
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
This is neither in http 2 format nor are there any upgrade headers which my server was designed to parse, which typically looks like this without using proxy:
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
My server was designed to parse this request as it is the upgrade request which contains the http 2 settings for the client.
So, my questions are when I get a request like this:
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
Do I respond back to the client in HTTP 1.1 or HTTP 2 Format? The doubt arises because in my client test case I have clearly specified version(HTTP_2) but the request has no upgrade headers, so should I maybe respond back in HTTP 1.1 format?
Is there any way to make the HttpClient include the Upgrade headers so I can respond back in HTTP 2 format?
EDIT.
To debug the response i created an simple TCP server running on port 1000 and proxied through it. I simply print the bytes received at this proxy and get the exact same output
//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));
}
}
}
}
CodePudding user response:
Evidently, OpenJDK's HttpClient
is only able to send HTTP/1.1 requests to a proxy.
Even if you specified HttpClient.Version.HTTP_2
, because there is a proxy, OpenJDK's HttpClient
ignores the version you specified, formats the request as HTTP/1.1 and sends it to the proxy.
OpenJDK's HttpClient
proxy capabilities are quite limited (for example, you cannot easily use a secure protocol to communicate to the proxy), so you may want to look into different HTTP clients.
[Disclaimer, I'm part of the Jetty team.]
Jetty's HttpClient
supports a more flexible proxy configuration, so you can specify what protocol the proxy speaks (and whether it is secure).
Note that you need the proxy cooperation to obtain what you want, because Connection
is a hop-by-hop header, so it is only valid on a single hop, i.e. from the client to the proxy.
The proxy can legitimately take the client's HTTP/1.1 upgrade request to HTTP/2, but then use HTTP/1.1 to talk to the server, get the HTTP/1.1 response back from the server and produce a 101 Upgrade
response followed by the an HTTP/2 response obtained converting to HTTP/2 the server's HTTP/1.1 response:
client proxy server
| ---- h1 upgrade ---> | |
| | -- h1 request --> |
| | <-- h1 response -- |
| <-- h1 101 upgrade -- |
| <--- h2c response --- |
A proxy that supports h2c
could instead do what you want:
client proxy server
| ---- h1 upgradeA ---> | |
| | --- h1 upgradeB ---> |
| | <-- h1 101 upgrade -- |
| | <--- h2c response ---- |
| <-- h1 101 upgrade -- |
| <--- h2c response ---- |
Note however that upgradeA
could be different from upgradeB
exactly because Connection
and Upgrade
are hop-by-hop headers, and the proxy may have a different HTTP/2 configuration with respect to the client (and therefore also send a different HTTP2-Settings
header).
If the communication between the client and the proxy is clear-text, you must have prior knowledge of whether the proxy supports h2c
, otherwise the client will default to HTTP/1.1 (like OpenJDK's HttpClient
does).
In contrast, Jetty's HttpClient
can be configured with the protocol you want to use to talk to the proxy.
Alternatively, you have to use a secure communication via TLS so that the client and the proxy can negotiate the protocol they want to speak (e.g. h2
).
The same holds for the proxy: if the communication with the server is clear-text (your case), the proxy must have prior knowledge that the server supports h2c
, so you need to configure the proxy with that information, otherwise the proxy will likely default to HTTP/1.1.
Obviously the proxy must support both HTTP/1.1 and HTTP/2.
If you are implementing the proxy, copying the Connection
(and other hop-by-hop headers) towards the server is technically wrong, but it may work in restricted, simpler, cases.
See this section of RFC 7230 that specifies how hop-by-hop headers should be removed by proxies.
FTR, this test shows that Jetty (client and server) can support any combination of protocols (HTTP/1.1 and HTTP/2, clear-text and secure) between the client, the proxy and the server.