netty: HTTP/2 Prior Knowledge does not appear to work with Http2Codec and Http2ServerDowngrader
Expected 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.Http2Serverwith 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
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Comments: 16 (12 by maintainers)
Commits related to this issue
- Add unit test — committed to netty/netty by chhsiao90 7 years ago
- Use addAfter in Http2Codec.handlerAdded(....) to ensure we not end up with incorrect handler order Motivation: When pipeline.replace(...) is used with the Http2Codec we will end up with an incorrect... — committed to netty/netty by normanmaurer 7 years ago
- Use addAfter in Http2Codec.handlerAdded(....) to ensure we not end up with incorrect handler order Motivation: When pipeline.replace(...) is used with the Http2Codec we will end up with an incorrect... — committed to netty/netty by normanmaurer 7 years ago
- Use addAfter in Http2Codec.handlerAdded(....) to ensure we not end up with incorrect handler order Motivation: When pipeline.replace(...) is used with the Http2Codec we will end up with an incorrect... — committed to netty/netty by normanmaurer 7 years ago
- Configures HTTP2 pipeline with more proper way Motivation: When we use pipeline.replace and we still had ongoing inbound, then there will be some problem that inbound message would go to wrong handl... — committed to liuzhengyang/netty by chhsiao90 7 years ago
- Configures HTTP2 pipeline with more proper way Motivation: When we use pipeline.replace and we still had ongoing inbound, then there will be some problem that inbound message would go to wrong handl... — committed to kiril-me/netty by chhsiao90 7 years ago
@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
ChannelHandlerContextwhen that Handler is no longer present in the pipeline and therefore does not have a defined position in that pipeline? After all, theChannelHandlerContextrepresents a Handler’s association with a pipeline–shouldn’t it throw anIllegalStateExceptionas 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
PriorKnowledgeHandleraddHttp2Codecafter itself (instead of callingreplace), propagate the data down the pipeline, and then remove itself from the pipeline?