Skip to content

Commit

Permalink
HTTP/2: Ensure newStream() is called only once per connection upgrade…
Browse files Browse the repository at this point in the history
… and the correct handler is used (netty#9396)

Motivation:

3062993 introduced some code change to move the responsibility of creating the stream for the upgrade to Http2FrameCodec. Unfortunaly this lead to the situation of having newStream().setStreamAndProperty(...) be called twice. Because of this we only ever saw the channelActive(...) on Http2StreamChannel but no other events as the mapping was replaced on the second newStream().setStreamAndProperty(...) call.

Beside this we also did not use the correct handler for the upgrade stream in some cases

Modifications:

- Just remove the Http2FrameCodec.onHttpClientUpgrade() method and so let the base class handle all of it. The stream is created correctly as part of the ConnectionListener implementation of Http2FrameCodec already.
- Consolidate logic of creating stream channels
- Adjust unit test to capture the bug

Result:

Fixes netty#9395
  • Loading branch information
normanmaurer authored Jul 23, 2019
1 parent 94f3930 commit 513e9f2
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,6 @@ void handlerAdded0(@SuppressWarnings("unsed") ChannelHandlerContext ctx) throws
// sub-class can override this for extra steps that needs to be done when the handler is added.
}

@Override
public void onHttpClientUpgrade() throws Http2Exception {
super.onHttpClientUpgrade();
// Now make a new Http2FrameStream, set it's underlying Http2Stream, and initialize it.
newStream().setStreamAndProperty(streamKey, connection().stream(HTTP_UPGRADE_STREAM_ID));
}

/**
* Handles the cleartext HTTP upgrade event. If an upgrade occurred, sends a simple response via
* HTTP/2 on stream 1 (the stream specifically reserved for cleartext HTTP upgrade).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,40 +149,34 @@ final void onHttp2Frame(ChannelHandlerContext ctx, Http2Frame frame) {
ctx.fireChannelRead(frame);
}

private void onHttp2UpgradeStreamInitialized(ChannelHandlerContext ctx, DefaultHttp2FrameStream stream) {
assert stream.state() == Http2Stream.State.HALF_CLOSED_LOCAL;
AbstractHttp2StreamChannel ch = new Http2MultiplexCodecStreamChannel(stream, null);
ch.closeOutbound();

// Add our upgrade handler to the channel and then register the channel.
// The register call fires the channelActive, etc.
ch.pipeline().addLast(upgradeStreamHandler);
ChannelFuture future = ctx.channel().eventLoop().register(ch);
if (future.isDone()) {
Http2MultiplexHandler.registerDone(future);
} else {
future.addListener(Http2MultiplexHandler.CHILD_CHANNEL_REGISTRATION_LISTENER);
}
}

@Override
final void onHttp2StreamStateChanged(ChannelHandlerContext ctx, DefaultHttp2FrameStream stream) {

switch (stream.state()) {
case HALF_CLOSED_LOCAL:
if (stream.id() == HTTP_UPGRADE_STREAM_ID) {
onHttp2UpgradeStreamInitialized(ctx, stream);
if (stream.id() != HTTP_UPGRADE_STREAM_ID) {
// Ignore everything which was not caused by an upgrade
break;
}
break;
// fall-through
case HALF_CLOSED_REMOTE:
// fall-through
case OPEN:
if (stream.attachment != null) {
// ignore if child channel was already created.
break;
}
// fall-trough
ChannelFuture future = ctx.channel().eventLoop().register(
new Http2MultiplexCodecStreamChannel(stream, inboundStreamHandler));
final Http2MultiplexCodecStreamChannel streamChannel;
// We need to handle upgrades special when on the client side.
if (stream.id() == HTTP_UPGRADE_STREAM_ID && !connection().isServer()) {
// Add our upgrade handler to the channel and then register the channel.
// The register call fires the channelActive, etc.
assert upgradeStreamHandler != null;
streamChannel = new Http2MultiplexCodecStreamChannel(stream, upgradeStreamHandler);
streamChannel.closeOutbound();
} else {
streamChannel = new Http2MultiplexCodecStreamChannel(stream, inboundStreamHandler);
}
ChannelFuture future = ctx.channel().eventLoop().register(streamChannel);
if (future.isDone()) {
Http2MultiplexHandler.registerDone(future);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,26 +214,24 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc
// Ignore everything which was not caused by an upgrade
break;
}
// We must have an upgrade handler or else we can't handle the stream
if (upgradeStreamHandler == null) {
throw connectionError(INTERNAL_ERROR,
"Client is misconfigured for upgrade requests");
}
// fall-trough
// fall-through
case HALF_CLOSED_REMOTE:
// fall-trough
// fall-through
case OPEN:
if (stream.attachment != null) {
// ignore if child channel was already created.
break;
}
final AbstractHttp2StreamChannel ch;
if (stream.state() == Http2Stream.State.HALF_CLOSED_LOCAL) {
ch = new Http2MultiplexHandlerStreamChannel(stream, null);
// We need to handle upgrades special when on the client side.
if (stream.id() == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID && !isServer(ctx)) {
// We must have an upgrade handler or else we can't handle the stream
if (upgradeStreamHandler == null) {
throw connectionError(INTERNAL_ERROR,
"Client is misconfigured for upgrade requests");
}
ch = new Http2MultiplexHandlerStreamChannel(stream, upgradeStreamHandler);
ch.closeOutbound();
// Add our upgrade handler to the channel and then register the channel.
// The register call fires the channelActive, etc.
ch.pipeline().addLast(upgradeStreamHandler);
} else {
ch = new Http2MultiplexHandlerStreamChannel(stream, inboundStreamHandler);
}
Expand Down Expand Up @@ -282,9 +280,13 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E
ctx.fireExceptionCaught(cause);
}

private static boolean isServer(ChannelHandlerContext ctx) {
return ctx.channel().parent() instanceof ServerChannel;
}

private void onHttp2GoAwayFrame(ChannelHandlerContext ctx, final Http2GoAwayFrame goAwayFrame) {
try {
final boolean server = ctx.channel().parent() instanceof ServerChannel;
final boolean server = isServer(ctx);
forEachActiveStream(new Http2FrameStreamVisitor() {
@Override
public boolean visit(Http2FrameStream stream) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public abstract class Http2MultiplexClientUpgradeTest<C extends Http2FrameCodec> {

@ChannelHandler.Sharable
final class NoopHandler extends ChannelInboundHandlerAdapter {
static final class NoopHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.channel().close();
}
}

private final class UpgradeHandler extends ChannelInboundHandlerAdapter {
private static final class UpgradeHandler extends ChannelInboundHandlerAdapter {
Http2Stream.State stateOnActive;
int streamId;
boolean channelInactiveCalled;

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Expand All @@ -46,6 +48,12 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception {
streamId = ch.stream().id();
super.channelActive(ctx);
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
channelInactiveCalled = true;
super.channelInactive(ctx);
}
}

protected abstract C newCodec(ChannelHandler upgradeHandler);
Expand All @@ -62,8 +70,10 @@ public void upgradeHandlerGetsActivated() throws Exception {

assertFalse(upgradeHandler.stateOnActive.localSideOpen());
assertTrue(upgradeHandler.stateOnActive.remoteSideOpen());
assertEquals(1, upgradeHandler.streamId);
assertNotNull(codec.connection().stream(Http2CodecUtil.HTTP_UPGRADE_STREAM_ID).getProperty(codec.streamKey));
assertEquals(Http2CodecUtil.HTTP_UPGRADE_STREAM_ID, upgradeHandler.streamId);
assertTrue(ch.finishAndReleaseAll());
assertTrue(upgradeHandler.channelInactiveCalled);
}

@Test(expected = Http2Exception.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.DefaultChannelId;
import io.netty.channel.ServerChannel;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpHeaders;
Expand All @@ -32,6 +34,7 @@
import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.mockito.Mockito;

public class Http2ServerUpgradeCodecTest {

Expand Down Expand Up @@ -63,7 +66,9 @@ private static void testUpgrade(Http2ConnectionHandler handler, ChannelHandler m
request.headers().set(HttpHeaderNames.UPGRADE, "h2c");
request.headers().set("HTTP2-Settings", "AAMAAABkAAQAAP__");

EmbeddedChannel channel = new EmbeddedChannel(new ChannelInboundHandlerAdapter());
ServerChannel parent = Mockito.mock(ServerChannel.class);
EmbeddedChannel channel = new EmbeddedChannel(parent, DefaultChannelId.newInstance(), true, false,
new ChannelInboundHandlerAdapter());
ChannelHandlerContext ctx = channel.pipeline().firstContext();
Http2ServerUpgradeCodec codec;
if (multiplexer == null) {
Expand Down

0 comments on commit 513e9f2

Please sign in to comment.