HTTP/2 Prior Knowledge does not appear to work with Http2Codec and Http2ServerDowngrader
See original GitHub issueExpected behavior
The CleartextHttp2ServerUpgradeHandler
should work with both HTTP Upgrade and Prior Knowledge when downgrading HTTP/2 requests internally.
Actual behavior
HTTP Upgrade works, but not Prior Knowledge. In the latter case, nghttp reports: (last_stream_id=0, error_code=PROTOCOL_ERROR(0x01), opaque_data(96)=[HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 000000040100000000])
Steps to reproduce
- Fire up
io.netty.example.http2.helloworld.server.Http2Server
with the modifications described in the next section - Install
nghttp
- Run
nghttp --upgrade --verbose --no-dep http://localhost:8080/
; it should succeed - Run
nghttp --verbose --no-dep http://localhost:8080/
; it fails like so:
[ 0.002] Connected
[ 0.002] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.002] send HEADERS frame <length=33, flags=0x05, stream_id=1>
; END_STREAM | END_HEADERS
(padlen=0)
; Open new stream
:method: GET
:path: /
:scheme: http
:authority: localhost:8080
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.23.1
[ 0.005] recv SETTINGS frame <length=6, flags=0x00, stream_id=0>
(niv=1)
[SETTINGS_MAX_HEADER_LIST_SIZE(0x06):8192]
[ 0.005] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.006] recv GOAWAY frame <length=104, flags=0x00, stream_id=0>
(last_stream_id=0, error_code=PROTOCOL_ERROR(0x01), opaque_data(96)=[HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 000000040100000000])
Some requests were not processed. total=1, processed=0
Minimal yet complete reproducer code (or URL to code)
Run the Http2Server
example code, using the following modified configureClearText
method:
private void configureClearText(SocketChannel ch) {
final ChannelPipeline p = ch.pipeline();
final HttpServerCodec sourceCodec = new HttpServerCodec();
final Http2Codec http2Codec = new Http2CodecBuilder(true, new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
p.addLast("downgrader", new Http2ServerDowngrader(true));
p.addLast("chunk-aggregator", new HttpObjectAggregator(1048576));
p.addLast("http1-handler", new HelloWorldHttp1Handler("Downgrade"));
}
}).build();
final UpgradeCodecFactory upgradeCodecFactory = new UpgradeCodecFactory() {
@Override
public UpgradeCodec newUpgradeCodec(CharSequence protocol) {
if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
return new Http2ServerUpgradeCodec("server-upgrade-codec", http2Codec);
} else {
return null;
}
}
};
final HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory);
final CleartextHttp2ServerUpgradeHandler cleartextHttp2ServerUpgradeHandler =
new CleartextHttp2ServerUpgradeHandler(sourceCodec, upgradeHandler, http2Codec);
p.addLast(cleartextHttp2ServerUpgradeHandler);
p.addLast(new SimpleChannelInboundHandler<HttpMessage>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpMessage msg) throws Exception {
// If this handler is hit then no upgrade has been attempted and the client is just talking HTTP.
System.err.println("Directly talking: " + msg.protocolVersion() + " (no upgrade was attempted)");
ChannelPipeline pipeline = ctx.pipeline();
ChannelHandlerContext thisCtx = pipeline.context(this);
pipeline.addAfter(thisCtx.name(), null, new HelloWorldHttp1Handler("Direct. No Upgrade Attempted."));
pipeline.replace(this, null, new HttpObjectAggregator(maxHttpContentLength));
ctx.fireChannelRead(ReferenceCountUtil.retain(msg));
}
});
p.addLast(new UserEventLogger());
}
Netty version
Latest 4.1
branch
JVM version (e.g. java -version
)
java 8u131
OS version (e.g. uname -a
)
macOS 10.12.4
Issue Analytics
- State:
- Created 6 years ago
- Comments:16 (12 by maintainers)
Top Results From Across the Web
API for prior knowledge HTTP/2 connection - GitLab - GNOME
Currently it's impossible for a libsoup client to connect to HTTP/2 servers without TLS, as that requires the client to have prior knowledge...
Read more >curl post request is not working with option --http2, but it works ...
When I am making curl post request with --http2 its saying "curl: (56) Recv failure: Connection reset by peer". but when I use...
Read more >HTTP/2 Support - Soteri Documentation
To take advantage, use a HTTP/2 compatible client. curl curl 7.47.0 and ... You can pass --http2-prior-knowledge to skip the HTTP/1.1 negotiation up...
Read more >RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)
Without additional information, prior support for HTTP/2 is not a strong ... If starting an HTTP/2 connection with prior knowledge of server support...
Read more >HTTP/2 with curl
With that bit set, libcurl will attempt to re-use existing HTTP/2 ... HTTP server there is a period during which it does not...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@rschmitt @Scottmitch @chhsiao90 https://github.com/netty/netty/pull/7035 is a better fix imho… PTAL
It sounds like there’s a much deeper design issue here. Why does Netty allow a Handler to pass an event via its
ChannelHandlerContext
when that Handler is no longer present in the pipeline and therefore does not have a defined position in that pipeline? After all, theChannelHandlerContext
represents a Handler’s association with a pipeline–shouldn’t it throw anIllegalStateException
as soon as you try to do this?And as for the various HTTP2 pipeline components, do they really need to rely on pipeline mutation so heavily? Your root cause analysis seems like a great example of how complex mutation-oriented programming can be even in a single-threaded context.
As for a fix, can
PriorKnowledgeHandler
addHttp2Codec
after itself (instead of callingreplace
), propagate the data down the pipeline, and then remove itself from the pipeline?