question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

HTTP/2 Prior Knowledge does not appear to work with Http2Codec and Http2ServerDowngrader

See original GitHub issue

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

  1. Fire up io.netty.example.http2.helloworld.server.Http2Server with the modifications described in the next section
  2. Install nghttp
  3. Run nghttp --upgrade --verbose --no-dep http://localhost:8080/; it should succeed
  4. 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:closed
  • Created 6 years ago
  • Comments:16 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
normanmaurercommented, Jul 29, 2017
1reaction
rschmittcommented, Jul 6, 2017

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, the ChannelHandlerContext represents a Handler’s association with a pipeline–shouldn’t it throw an IllegalStateException 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 add Http2Codec after itself (instead of calling replace), propagate the data down the pipeline, and then remove itself from the pipeline?

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found