H2C upgrades should be ineligible for flow control (#7400)
H2C upgrades should be ineligible for flow control Motivation: When the h2c upgrade request is too big, the Http2FrameCodec complains it's too big for flow control reasons, even though it's ineligible for flow control. Modifications: Specially mark upgrade streams and make Http2FrameCodec know not to try to flow control on those streams. Result: Servers won't barf when they receive an upgrade request with a fat payload. [Fixes #7280]
This commit is contained in:
parent
b215794de3
commit
0cac1a6c8c
@ -145,6 +145,7 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
|
|||||||
private static final InternalLogger LOG = InternalLoggerFactory.getInstance(Http2FrameCodec.class);
|
private static final InternalLogger LOG = InternalLoggerFactory.getInstance(Http2FrameCodec.class);
|
||||||
|
|
||||||
private final PropertyKey streamKey;
|
private final PropertyKey streamKey;
|
||||||
|
private final PropertyKey upgradeKey;
|
||||||
|
|
||||||
private final Integer initialFlowControlWindowSize;
|
private final Integer initialFlowControlWindowSize;
|
||||||
|
|
||||||
@ -161,6 +162,7 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
|
|||||||
connection().addListener(new ConnectionListener());
|
connection().addListener(new ConnectionListener());
|
||||||
connection().remote().flowController().listener(new Http2RemoteFlowControllerListener());
|
connection().remote().flowController().listener(new Http2RemoteFlowControllerListener());
|
||||||
streamKey = connection().newKey();
|
streamKey = connection().newKey();
|
||||||
|
upgradeKey = connection().newKey();
|
||||||
initialFlowControlWindowSize = initialSettings.initialWindowSize();
|
initialFlowControlWindowSize = initialSettings.initialWindowSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +249,7 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
|
|||||||
}
|
}
|
||||||
upgrade.upgradeRequest().headers().setInt(
|
upgrade.upgradeRequest().headers().setInt(
|
||||||
HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), HTTP_UPGRADE_STREAM_ID);
|
HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), HTTP_UPGRADE_STREAM_ID);
|
||||||
|
stream.setProperty(upgradeKey, true);
|
||||||
InboundHttpToHttp2Adapter.handle(
|
InboundHttpToHttp2Adapter.handle(
|
||||||
ctx, connection(), decoder().frameListener(), upgrade.upgradeRequest().retain());
|
ctx, connection(), decoder().frameListener(), upgrade.upgradeRequest().retain());
|
||||||
} finally {
|
} finally {
|
||||||
@ -312,6 +315,14 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
|
|||||||
|
|
||||||
final boolean consumeBytes(int streamId, int bytes) throws Http2Exception {
|
final boolean consumeBytes(int streamId, int bytes) throws Http2Exception {
|
||||||
Http2Stream stream = connection().stream(streamId);
|
Http2Stream stream = connection().stream(streamId);
|
||||||
|
// upgraded requests are ineligible for stream control
|
||||||
|
if (streamId == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
|
||||||
|
Boolean upgraded = stream.getProperty(upgradeKey);
|
||||||
|
if (Boolean.TRUE.equals(upgraded)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return connection().local().flowController().consumeBytes(stream, bytes);
|
return connection().local().flowController().consumeBytes(stream, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,9 @@ public class InboundHttpToHttp2Adapter extends ChannelInboundHandlerAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note that this may behave strangely when used for the initial upgrade
|
||||||
|
// message when using h2c, since that message is ineligible for flow
|
||||||
|
// control, but there is not yet an API for signaling that.
|
||||||
static void handle(ChannelHandlerContext ctx, Http2Connection connection,
|
static void handle(ChannelHandlerContext ctx, Http2Connection connection,
|
||||||
Http2FrameListener listener, FullHttpMessage message) throws Http2Exception {
|
Http2FrameListener listener, FullHttpMessage message) throws Http2Exception {
|
||||||
try {
|
try {
|
||||||
|
@ -21,6 +21,7 @@ import io.netty.buffer.UnpooledByteBufAllocator;
|
|||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelFutureListener;
|
import io.netty.channel.ChannelFutureListener;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.channel.embedded.EmbeddedChannel;
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
||||||
@ -37,6 +38,7 @@ import io.netty.handler.codec.http2.Http2Stream.State;
|
|||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import io.netty.util.AbstractReferenceCounted;
|
import io.netty.util.AbstractReferenceCounted;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
import io.netty.util.ReferenceCounted;
|
import io.netty.util.ReferenceCounted;
|
||||||
import io.netty.util.concurrent.DefaultPromise;
|
import io.netty.util.concurrent.DefaultPromise;
|
||||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||||
@ -666,6 +668,47 @@ public class Http2FrameCodecTest {
|
|||||||
assertEquals(1, upgradeEvent.refCnt());
|
assertEquals(1, upgradeEvent.refCnt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void upgradeWithoutFlowControlling() throws Exception {
|
||||||
|
channel.pipeline().addAfter(http2HandlerCtx.name(), null, new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
if (msg instanceof Http2DataFrame) {
|
||||||
|
// Simulate consuming the frame and update the flow-controller.
|
||||||
|
Http2DataFrame data = (Http2DataFrame) msg;
|
||||||
|
ctx.writeAndFlush(new DefaultHttp2WindowUpdateFrame(data.initialFlowControlledBytes())
|
||||||
|
.stream(data.stream())).addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
Throwable cause = future.cause();
|
||||||
|
if (cause != null) {
|
||||||
|
ctx.fireExceptionCaught(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ReferenceCountUtil.release(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frameListener.onHeadersRead(http2HandlerCtx, Http2CodecUtil.HTTP_UPGRADE_STREAM_ID, request, 31, false);
|
||||||
|
|
||||||
|
// Using reflect as the constructor is package-private and the class is final.
|
||||||
|
Constructor<UpgradeEvent> constructor =
|
||||||
|
UpgradeEvent.class.getDeclaredConstructor(CharSequence.class, FullHttpRequest.class);
|
||||||
|
|
||||||
|
// Check if we could make it accessible which may fail on java9.
|
||||||
|
Assume.assumeTrue(ReflectionUtil.trySetAccessible(constructor) == null);
|
||||||
|
|
||||||
|
String longString = new String(new char[70000]).replace("\0", "*");
|
||||||
|
DefaultFullHttpRequest request =
|
||||||
|
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/", bb(longString));
|
||||||
|
|
||||||
|
HttpServerUpgradeHandler.UpgradeEvent upgradeEvent = constructor.newInstance(
|
||||||
|
"HTTP/2", request);
|
||||||
|
channel.pipeline().fireUserEventTriggered(upgradeEvent);
|
||||||
|
}
|
||||||
|
|
||||||
private static ChannelPromise anyChannelPromise() {
|
private static ChannelPromise anyChannelPromise() {
|
||||||
return any(ChannelPromise.class);
|
return any(ChannelPromise.class);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user