Explict always call ctx.read() when AUTO_READ is false and HTTP/2 is used. (#8647)

Motivation:

We should always call ctx.read() even when AUTO_READ is false as flow-control is enforced by the HTTP/2 protocol.

See also https://tools.ietf.org/html/rfc7540#section-5.2.2.

We already did this before but not explicit and only did so because of some implementation details of ByteToMessageDecoder. It's better to be explicit here to not risk of breakage later on.

Modifications:

- Ensure we always call ctx.read() when AUTO_READ is false
- Add unit test.

Result:

No risk of staling the connection when HTTP/2 is used.
This commit is contained in:
Norman Maurer 2018-12-13 19:02:20 +01:00
parent 7f22743d92
commit 75e6f597ce
2 changed files with 27 additions and 2 deletions

View File

@ -527,8 +527,18 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
}
}
void channelReadComplete0(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
final void channelReadComplete0(ChannelHandlerContext ctx) {
// Discard bytes of the cumulation buffer if needed.
discardSomeReadBytes();
// Ensure we never stale the HTTP/2 Channel. Flow-control is enforced by HTTP/2.
//
// See https://tools.ietf.org/html/rfc7540#section-5.2.2
if (!ctx.channel().config().isAutoRead()) {
ctx.read();
}
ctx.fireChannelReadComplete();
}
/**

View File

@ -22,8 +22,10 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelConfig;
import io.netty.channel.DefaultChannelPromise;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
@ -142,6 +144,11 @@ public class Http2ConnectionHandlerTest {
promise = new DefaultChannelPromise(channel, ImmediateEventExecutor.INSTANCE);
voidPromise = new DefaultChannelPromise(channel, ImmediateEventExecutor.INSTANCE);
when(channel.metadata()).thenReturn(new ChannelMetadata(false));
DefaultChannelConfig config = new DefaultChannelConfig(channel);
when(channel.config()).thenReturn(config);
Throwable fakeException = new RuntimeException("Fake exception");
when(encoder.connection()).thenReturn(connection);
when(decoder.connection()).thenReturn(connection);
@ -684,6 +691,14 @@ public class Http2ConnectionHandlerTest {
verify(ctx, times(1)).flush();
}
@Test
public void channelReadCompleteCallsReadWhenAutoReadFalse() throws Exception {
channel.config().setAutoRead(false);
handler = newHandler();
handler.channelReadComplete(ctx);
verify(ctx, times(1)).read();
}
@Test
public void channelClosedDoesNotThrowPrefaceException() throws Exception {
when(connection.isServer()).thenReturn(true);