-
Clarification
-
Resolution: Unresolved
-
Major
-
None
-
2.2.22.Final
-
None
-
None
When checking Servlet API's javax.servlet.http.HttpServletRequest#newPushBuilder() and HTTP/2 (in Pax Web project) I'm getting exception at HttpClient side:
org.apache.hc.core5.http.ProtocolException: Header 'host: 127.0.0.1:34533' is illegal for HTTP/2 messages at org.apache.hc.core5.http2.impl.DefaultH2RequestConverter.convert(DefaultH2RequestConverter.java:112) at org.apache.hc.core5.http2.impl.nio.ClientPushH2StreamHandler.consumePromise(ClientPushH2StreamHandler.java:105) at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer$H2Stream.consumePromise(AbstractH2StreamMultiplexer.java:1619) at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer.consumePushPromiseFrame(AbstractH2StreamMultiplexer.java:1070) at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer.consumeFrame(AbstractH2StreamMultiplexer.java:975) at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer.onInput(AbstractH2StreamMultiplexer.java:445) at org.apache.hc.core5.http2.impl.nio.AbstractH2IOEventHandler.inputReady(AbstractH2IOEventHandler.java:65) at org.apache.hc.core5.http2.impl.nio.ClientH2IOEventHandler.inputReady(ClientH2IOEventHandler.java:39) at org.apache.hc.client5.http.impl.async.LoggingIOSession$1.inputReady(LoggingIOSession.java:238) at org.apache.hc.core5.reactor.ssl.SSLIOSession.decryptData(SSLIOSession.java:600) at org.apache.hc.core5.reactor.ssl.SSLIOSession.access$200(SSLIOSession.java:74) at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.inputReady(SSLIOSession.java:202) at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:142) at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51) at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:178) at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:127) at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:86) at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44) at java.base/java.lang.Thread.run(Thread.java:834)
This is the PUSH_PROMISE log from HttpClient 5.2.
18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:73) : c-0000000000 << stream 1 frame: PUSH_PROMISE (0x5); flags: END_HEADERS (0x4); length: 137 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << Promised stream 2 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << ?.?.A...\ .p.e.. 3f e1 3f 87 41 8b 08 9d 5c 0b 81 70 dc 65 a6 d9 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << gD.a%BX......... 67 44 8c 61 25 42 58 90 b2 8e da 12 b9 10 8f 82 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << z....r..)..z...1 7a a0 86 b1 92 72 ad 8d 29 ae 14 7a a8 97 a8 31 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << jK .%....}..B.. 6a 4b 0d ae 25 c2 a7 f5 94 7d c6 c0 42 b8 17 0b 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << ..s..)...`"up... bf df 73 9c 9d 29 ad 17 18 60 22 75 70 2e 05 c3 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << q..e..T%.U!|.:i. 71 96 9b 65 96 12 54 25 83 55 21 7c af 3a 69 a3 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << @.....iP....a%BX 40 89 f2 b5 85 ed 69 50 95 ab 0f 8c 61 25 42 58 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << 5R.....?f...\ .p 35 52 17 ca f3 a6 9a 3f 66 8b 08 9d 5c 0b 81 70 18:14:49 {httpclient-dispatch-1} DEBUG (LogAppendable.java:63) : c-0000000000 << .e..g dc 65 a6 d9 67 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << :scheme: https 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << :authority: 127.0.0.1:34533 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << :path: /test/default.css 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << :method: GET 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << user-agent: Apache-HttpAsyncClient/5.2.1 (Java/11.0.17) 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << referer: https://127.0.0.1:34533/test/index.html 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << x-request-p1: /test/index.html 18:14:49 {httpclient-dispatch-1} DEBUG (HttpAsyncClientProtocolNegotiationStarter.java:193) : c-0000000000 << host: 127.0.0.1:34533
It works fine when I explicitly remove Host header when sending push promise:
PushBuilder pushBuilder = req.newPushBuilder(); if (pushBuilder != null) { pushBuilder.removeHeader("host"); pushBuilder.path("test/default.css").addHeader("X-Request-P1", req.getRequestURI()).push(); pushBuilder.path("test/app.js").removeHeader("X-Request-P1").addHeader("X-Request-P2", req.getRequestURI()).push(); }
I checked that io.undertow.server.protocol.http2.Http2ReceiveListener#handleRequests() explicitly calls:
exchange.getRequestHeaders().put(Headers.HOST, exchange.getRequestHeaders().getFirst(AUTHORITY));
https://httpwg.org/specs/rfc7540.html (section "8.1.2.3. Request Pseudo-Header Fields") says that:
Clients that generate HTTP/2 requests directly SHOULD use the :authority pseudo-header field instead of the Host header field.
I think it's still a transition issue when switching from HTTP/1.1 to HTTP/2, but:
- Tomcat doesn't do it
- Undertow returns non-null value for request.getHeader("Host") even if the client doesn't send the header at all - so it may be a bit confusing...
Please check related:
- https://issues.apache.org/jira/browse/HTTPCORE-692
- https://github.com/apache/httpcomponents-core/commit/6741e3c
I'm not sure if io.undertow.server.protocol.http2.Http2ReceiveListener#handleRequests() should artificially set the Host header for pure HTTP/2 requests.