diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java index 7b130bf6f3..da2b27a1d7 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java @@ -315,8 +315,9 @@ public class Http2FrameCodec extends Http2ConnectionHandler { final boolean consumeBytes(int streamId, int bytes) throws Http2Exception { Http2Stream stream = connection().stream(streamId); - // upgraded requests are ineligible for stream control - if (streamId == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) { + // Upgraded requests are ineligible for stream control. We add the null check + // in case the stream has been deregistered. + if (stream != null && streamId == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) { Boolean upgraded = stream.getProperty(upgradeKey); if (Boolean.TRUE.equals(upgraded)) { return false; diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameCodecTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameCodecTest.java index 7490701bb5..2123986047 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameCodecTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameCodecTest.java @@ -209,6 +209,20 @@ public class Http2FrameCodecTest { assertTrue(channel.isActive()); } + @Test + public void flowControlShouldBeResilientToMissingStreams() throws Http2Exception { + Http2Connection conn = new DefaultHttp2Connection(true); + Http2ConnectionEncoder enc = new DefaultHttp2ConnectionEncoder(conn, new DefaultHttp2FrameWriter()); + Http2ConnectionDecoder dec = new DefaultHttp2ConnectionDecoder(conn, enc, new DefaultHttp2FrameReader()); + Http2FrameCodec codec = new Http2FrameCodec(enc, dec, new Http2Settings()); + EmbeddedChannel em = new EmbeddedChannel(codec); + + // We call #consumeBytes on a stream id which has not been seen yet to emulate the case + // where a stream is deregistered which in reality can happen in response to a RST. + assertFalse(codec.consumeBytes(1, 1)); + assertTrue(em.finishAndReleaseAll()); + } + @Test public void entityRequestEntityResponse() throws Exception { frameListener.onHeadersRead(http2HandlerCtx, 1, request, 0, false);