HTTP2: Close encoder and decoder on channelInactive and initialize clientPrefaceString on handlerAdded.
Motivation: - The encoder and decoder should be closed right after the handler releases its resources. - The clientPrefaceString is allocated in the constructor but releases in handlerRemoved. If the handler is never added to the pipeline, the clientPrefaceString will never be released. Modifications: - Call encoder.close() and decoder.close() on channelInactive. - Release the clientPrefaceString on handlerRemoved. Result: - The encoder and decoder get closed right after the handler's resources are freed. - It's easier to verify that the clientPrefaceString will also get released.
This commit is contained in:
parent
3405aee2ab
commit
9bad408de5
@ -15,6 +15,7 @@
|
|||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
|
||||||
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
||||||
@ -99,8 +100,6 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
|||||||
if (encoder.connection() != decoder.connection()) {
|
if (encoder.connection() != decoder.connection()) {
|
||||||
throw new IllegalArgumentException("Encoder and Decoder do not share the same connection object");
|
throw new IllegalArgumentException("Encoder and Decoder do not share the same connection object");
|
||||||
}
|
}
|
||||||
|
|
||||||
clientPrefaceString = clientPrefaceString(encoder.connection());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2Connection connection() {
|
public Http2Connection connection() {
|
||||||
@ -159,14 +158,21 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
clientPrefaceString = clientPrefaceString(encoder.connection());
|
||||||
// This handler was just added to the context. In case it was handled after
|
// This handler was just added to the context. In case it was handled after
|
||||||
// the connection became active, send the connection preface now.
|
// the connection became active, send the connection preface now.
|
||||||
sendPreface(ctx);
|
sendPreface(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the {@code clientPrefaceString}. Any active streams will be left in the open.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
||||||
dispose();
|
if (clientPrefaceString != null) {
|
||||||
|
clientPrefaceString.release();
|
||||||
|
clientPrefaceString = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -226,11 +232,19 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
try {
|
||||||
ChannelFuture future = ctx.newSucceededFuture();
|
ChannelFuture future = ctx.newSucceededFuture();
|
||||||
final Collection<Http2Stream> streams = connection().activeStreams();
|
final Collection<Http2Stream> streams = connection().activeStreams();
|
||||||
for (Http2Stream s : streams.toArray(new Http2Stream[streams.size()])) {
|
for (Http2Stream s : streams.toArray(new Http2Stream[streams.size()])) {
|
||||||
closeStream(s, future);
|
closeStream(s, future);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
encoder().close();
|
||||||
|
} finally {
|
||||||
|
decoder().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
super.channelInactive(ctx);
|
super.channelInactive(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,18 +464,6 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
|||||||
ChannelFutureListener.CLOSE_ON_FAILURE);
|
ChannelFutureListener.CLOSE_ON_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of all resources.
|
|
||||||
*/
|
|
||||||
private void dispose() {
|
|
||||||
encoder.close();
|
|
||||||
decoder.close();
|
|
||||||
if (clientPrefaceString != null) {
|
|
||||||
clientPrefaceString.release();
|
|
||||||
clientPrefaceString = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes the client connection preface string from the input buffer.
|
* Decodes the client connection preface string from the input buffer.
|
||||||
*
|
*
|
||||||
|
@ -117,22 +117,27 @@ public class Http2ConnectionHandlerTest {
|
|||||||
when(ctx.newSucceededFuture()).thenReturn(future);
|
when(ctx.newSucceededFuture()).thenReturn(future);
|
||||||
when(ctx.newPromise()).thenReturn(promise);
|
when(ctx.newPromise()).thenReturn(promise);
|
||||||
when(ctx.write(any())).thenReturn(future);
|
when(ctx.write(any())).thenReturn(future);
|
||||||
|
|
||||||
handler = newHandler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Http2ConnectionHandler newHandler() {
|
private Http2ConnectionHandler newHandler() throws Exception {
|
||||||
return new Http2ConnectionHandler(decoderBuilder, encoderBuilder);
|
Http2ConnectionHandler handler = new Http2ConnectionHandler(decoderBuilder, encoderBuilder);
|
||||||
|
handler.handlerAdded(ctx);
|
||||||
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
|
if (handler != null) {
|
||||||
handler.handlerRemoved(ctx);
|
handler.handlerRemoved(ctx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void clientShouldSendClientPrefaceStringWhenActive() throws Exception {
|
public void clientShouldSendClientPrefaceStringWhenActive() throws Exception {
|
||||||
when(connection.isServer()).thenReturn(false);
|
when(connection.isServer()).thenReturn(false);
|
||||||
|
when(channel.isActive()).thenReturn(false);
|
||||||
|
handler = newHandler();
|
||||||
|
when(channel.isActive()).thenReturn(true);
|
||||||
handler.channelActive(ctx);
|
handler.channelActive(ctx);
|
||||||
verify(ctx).write(eq(connectionPrefaceBuf()));
|
verify(ctx).write(eq(connectionPrefaceBuf()));
|
||||||
}
|
}
|
||||||
@ -140,6 +145,9 @@ public class Http2ConnectionHandlerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void serverShouldNotSendClientPrefaceStringWhenActive() throws Exception {
|
public void serverShouldNotSendClientPrefaceStringWhenActive() throws Exception {
|
||||||
when(connection.isServer()).thenReturn(true);
|
when(connection.isServer()).thenReturn(true);
|
||||||
|
when(channel.isActive()).thenReturn(false);
|
||||||
|
handler = newHandler();
|
||||||
|
when(channel.isActive()).thenReturn(true);
|
||||||
handler.channelActive(ctx);
|
handler.channelActive(ctx);
|
||||||
verify(ctx, never()).write(eq(connectionPrefaceBuf()));
|
verify(ctx, never()).write(eq(connectionPrefaceBuf()));
|
||||||
}
|
}
|
||||||
@ -158,18 +166,21 @@ public class Http2ConnectionHandlerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void serverReceivingValidClientPrefaceStringShouldContinueReadingFrames() throws Exception {
|
public void serverReceivingValidClientPrefaceStringShouldContinueReadingFrames() throws Exception {
|
||||||
when(connection.isServer()).thenReturn(true);
|
when(connection.isServer()).thenReturn(true);
|
||||||
|
handler = newHandler();
|
||||||
handler.channelRead(ctx, connectionPrefaceBuf());
|
handler.channelRead(ctx, connectionPrefaceBuf());
|
||||||
verify(decoder).decodeFrame(eq(ctx), any(ByteBuf.class), Matchers.<List<Object>>any());
|
verify(decoder).decodeFrame(eq(ctx), any(ByteBuf.class), Matchers.<List<Object>>any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelInactiveShouldCloseStreams() throws Exception {
|
public void channelInactiveShouldCloseStreams() throws Exception {
|
||||||
|
handler = newHandler();
|
||||||
handler.channelInactive(ctx);
|
handler.channelInactive(ctx);
|
||||||
verify(stream).close();
|
verify(stream).close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void connectionErrorShouldStartShutdown() throws Exception {
|
public void connectionErrorShouldStartShutdown() throws Exception {
|
||||||
|
handler = newHandler();
|
||||||
Http2Exception e = new Http2Exception(PROTOCOL_ERROR);
|
Http2Exception e = new Http2Exception(PROTOCOL_ERROR);
|
||||||
when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
|
when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
|
||||||
handler.exceptionCaught(ctx, e);
|
handler.exceptionCaught(ctx, e);
|
||||||
@ -178,4 +189,14 @@ public class Http2ConnectionHandlerTest {
|
|||||||
captor.capture(), eq(promise));
|
captor.capture(), eq(promise));
|
||||||
captor.getValue().release();
|
captor.getValue().release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void encoderAndDecoderAreClosedOnChannelInactive() throws Exception {
|
||||||
|
handler = newHandler();
|
||||||
|
handler.channelActive(ctx);
|
||||||
|
when(channel.isActive()).thenReturn(false);
|
||||||
|
handler.channelInactive(ctx);
|
||||||
|
verify(encoder).close();
|
||||||
|
verify(decoder).close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user