From 0c817d61b3aa4af7bf5e1454f959aaa5e6e470d3 Mon Sep 17 00:00:00 2001 From: nmittler Date: Fri, 15 Aug 2014 14:57:02 -0700 Subject: [PATCH] Upgrading to HTTP/2 draft 14 framing Motivation: HTTP/2 draft 14 came out a couple of weeks ago and we need to keep up with the spec. Modifications: - Removed use of segment throughout. - Added new setting for MAX_FRAME_SIZE. Used by the frame reader/writer rather than a constant. - Added new setting for MAX_HEADER_LIST_SIZE. This is currently unused. - Expanded the header size to 9 bytes. The frame length field is now 3 bytes and added logic for checking that it falls within the valid range. Result: Netty will support HTTP/2 draft 14 framing. There will still be some work to do to be compliant with the HTTP adaptation layer. --- codec-http2/pom.xml | 2 +- .../http2/AbstractHttp2ConnectionHandler.java | 80 +++++++++---- .../codec/http2/DefaultHttp2FrameReader.java | 66 +++++++--- .../codec/http2/DefaultHttp2FrameWriter.java | 60 +++++++--- .../codec/http2/DefaultHttp2Headers.java | 72 +++++++++-- .../http2/DefaultHttp2HeadersDecoder.java | 36 +++++- .../http2/DefaultHttp2HeadersEncoder.java | 51 ++++++-- .../DefaultHttp2InboundFlowController.java | 2 +- .../DefaultHttp2OutboundFlowController.java | 49 +++++--- .../DelegatingHttp2ConnectionHandler.java | 22 ++-- .../DelegatingHttp2HttpConnectionHandler.java | 6 +- .../handler/codec/http2/Http2CodecUtil.java | 50 +++++++- .../netty/handler/codec/http2/Http2Flags.java | 19 --- .../codec/http2/Http2FrameAdapter.java | 8 +- .../handler/codec/http2/Http2FrameLogger.java | 19 ++- .../codec/http2/Http2FrameObserver.java | 11 +- .../handler/codec/http2/Http2FrameReader.java | 20 ++++ .../handler/codec/http2/Http2FrameWriter.java | 29 ++++- .../handler/codec/http2/Http2Headers.java | 71 ++++++++--- .../codec/http2/Http2HeadersDecoder.java | 10 ++ .../codec/http2/Http2HeadersEncoder.java | 10 ++ .../http2/Http2InboundFlowController.java | 3 +- .../codec/http2/Http2InboundFrameLogger.java | 37 ++++-- .../codec/http2/Http2OrHttpChooser.java | 3 +- .../http2/Http2OutboundFlowController.java | 11 +- .../codec/http2/Http2OutboundFrameLogger.java | 38 ++++-- .../handler/codec/http2/Http2Settings.java | 113 ++++++++++++++++-- .../http2/InboundHttp2ToHttpAdapter.java | 26 ++-- .../codec/http2/DefaultHttp2FrameIOTest.java | 44 +++---- .../http2/DefaultHttp2HeadersDecoderTest.java | 68 +++++++++++ .../http2/DefaultHttp2HeadersEncoderTest.java | 55 +++++++++ .../codec/http2/DefaultHttp2HeadersTest.java | 33 +++++ ...DefaultHttp2InboundFlowControllerTest.java | 2 +- ...efaultHttp2OutboundFlowControllerTest.java | 40 +++++-- .../DelegatingHttp2ConnectionHandlerTest.java | 66 +++++----- ...egatingHttp2HttpConnectionHandlerTest.java | 77 +++++------- .../http2/Http2ConnectionRoundtripTest.java | 24 ++-- .../codec/http2/Http2FrameRoundtripTest.java | 32 ++--- .../codec/http2/Http2SettingsTest.java | 7 ++ .../http2/InboundHttp2ToHttpAdapterTest.java | 93 +++++++------- .../example/http2/client/Http2Client.java | 7 +- .../http2/client/HttpResponseHandler.java | 6 +- .../http2/server/HelloWorldHttp2Handler.java | 11 +- 43 files changed, 1056 insertions(+), 433 deletions(-) create mode 100644 codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java create mode 100644 codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java diff --git a/codec-http2/pom.xml b/codec-http2/pom.xml index 56f27016d0..f5e9c9cfb6 100644 --- a/codec-http2/pom.xml +++ b/codec-http2/pom.xml @@ -43,7 +43,7 @@ com.twitter hpack - 0.8.0 + 0.9.0 org.mockito diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java index f53ca9d1ac..c05b49a71a 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java @@ -210,6 +210,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode settings.initialWindowSize(inboundFlow.initialInboundWindowSize()); settings.maxConcurrentStreams(connection.remote().maxStreams()); settings.headerTableSize(frameReader.maxHeaderTableSize()); + settings.maxFrameSize(frameReader.maxFrameSize()); + settings.maxHeaderListSize(frameReader.maxHeaderListSize()); if (!connection.isServer()) { // Only set the pushEnabled flag if this is a client endpoint. settings.pushEnabled(connection.local().allowPushTo()); @@ -222,7 +224,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode */ @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) throws Http2Exception { + boolean endOfStream) throws Http2Exception { } /** @@ -232,7 +234,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode */ @Override public final void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int padding, boolean endStream, boolean endSegment) throws Http2Exception { + int padding, boolean endStream) throws Http2Exception { } /** @@ -240,8 +242,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode */ @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, - boolean endSegment) throws Http2Exception { + int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) + throws Http2Exception { } /** @@ -338,7 +340,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode protected ChannelFuture writeData(final ChannelHandlerContext ctx, final ChannelPromise promise, int streamId, final ByteBuf data, int padding, - boolean endStream, boolean endSegment) { + boolean endStream) { try { if (connection.isGoAway()) { throw protocolError("Sending data after connection going away."); @@ -348,7 +350,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE); // Hand control of the frame to the flow controller. - outboundFlow.sendFlowControlled(streamId, data, padding, endStream, endSegment, + outboundFlow.sendFlowControlled(streamId, data, padding, endStream, new FlowControlWriter(ctx, data, promise)); return promise; @@ -358,14 +360,14 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode } protected ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, - int streamId, Http2Headers headers, int padding, boolean endStream, boolean endSegment) { + int streamId, Http2Headers headers, int padding, boolean endStream) { return writeHeaders(ctx, promise, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, - padding, endStream, endSegment); + padding, endStream); } protected ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, Http2Headers headers, int streamDependency, short weight, - boolean exclusive, int padding, boolean endStream, boolean endSegment) { + boolean exclusive, int padding, boolean endStream) { try { if (connection.isGoAway()) { throw protocolError("Sending headers after connection going away."); @@ -393,7 +395,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode } ChannelFuture future = frameWriter.writeHeaders(ctx, promise, streamId, headers, streamDependency, - weight, exclusive, padding, endStream, endSegment); + weight, exclusive, padding, endStream); // If the headers are the end of the stream, close it now. if (endStream) { @@ -506,6 +508,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode frameReader.readFrame(ctx, in, internalFrameObserver); } catch (Http2Exception e) { onHttp2Exception(ctx, e); + } catch (Throwable e) { + onHttp2Exception(ctx, new Http2Exception(Http2Error.INTERNAL_ERROR, e.getMessage(), e)); } } @@ -733,6 +737,21 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode frameWriter.maxHeaderTableSize(headerTableSize); } + Integer maxHeaderListSize = settings.maxHeaderListSize(); + if (maxHeaderListSize != null) { + frameWriter.maxHeaderListSize(maxHeaderListSize); + } + + Integer maxFrameSize = settings.maxFrameSize(); + if (maxFrameSize != null) { + try { + frameWriter.maxFrameSize(maxFrameSize); + } catch (IllegalArgumentException e) { + throw new Http2Exception(Http2Error.FRAME_SIZE_ERROR, + "Invalid MAX_FRAME_SIZE specified in received settings: " + maxFrameSize); + } + } + Integer initialWindowSize = settings.initialWindowSize(); if (initialWindowSize != null) { outboundFlow.initialOutboundWindowSize(initialWindowSize); @@ -760,7 +779,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode @Override public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) throws Http2Exception { + boolean endOfStream) throws Http2Exception { verifyPrefaceReceived(); // Check if we received a data frame for a stream which is half-closed @@ -768,7 +787,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL); // Apply flow control. - inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, endOfSegment, + inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, new Http2InboundFlowController.FrameWriter() { @Override public void writeFrame(int streamId, int windowSizeIncrement) @@ -785,8 +804,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode return; } - AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream, - endOfSegment); + AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream); if (endOfStream) { closeRemoteSide(stream, ctx.newSucceededFuture()); @@ -824,15 +842,15 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int padding, boolean endStream, boolean endSegment) throws Http2Exception { + int padding, boolean endStream) throws Http2Exception { onHeadersRead(ctx, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, padding, - endStream, endSegment); + endStream); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, - boolean endStream, boolean endSegment) throws Http2Exception { + boolean endStream) throws Http2Exception { verifyPrefaceReceived(); Http2Stream stream = connection.stream(streamId); @@ -865,7 +883,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode } AbstractHttp2ConnectionHandler.this.onHeadersRead(ctx, streamId, headers, streamDependency, - weight, exclusive, padding, endStream, endSegment); + weight, exclusive, padding, endStream); // If the headers completes this stream, close it. if (endStream) { @@ -948,6 +966,21 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode frameReader.maxHeaderTableSize(headerTableSize); } + Integer maxHeaderListSize = settings.maxHeaderListSize(); + if (maxHeaderListSize != null) { + frameReader.maxHeaderListSize(maxHeaderListSize); + } + + Integer maxFrameSize = settings.maxFrameSize(); + if (maxFrameSize != null) { + try { + frameReader.maxFrameSize(maxFrameSize); + } catch (IllegalArgumentException e) { + throw new Http2Exception(Http2Error.FRAME_SIZE_ERROR, + "Invalid MAX_FRAME_SIZE specified in sent settings: " + maxFrameSize); + } + } + Integer initialWindowSize = settings.initialWindowSize(); if (initialWindowSize != null) { inboundFlow.initialInboundWindowSize(initialWindowSize); @@ -1098,8 +1131,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode } @Override - public void writeFrame(int streamId, ByteBuf data, int padding, - boolean endStream, boolean endSegment) { + public void writeFrame(int streamId, ByteBuf data, int padding, boolean endStream) { if (promise.isDone()) { // Most likely the write already failed. Just release the // buffer. @@ -1128,8 +1160,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode // Write the frame. ChannelFuture future = - frameWriter.writeData(ctx, chunkPromise, streamId, data, padding, endStream, - endSegment); + frameWriter.writeData(ctx, chunkPromise, streamId, data, padding, endStream); // Close the connection on write failures that leave the outbound // flow control window in a corrupt state. @@ -1160,6 +1191,11 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode failAllPromises(cause); } + @Override + public int maxFrameSize() { + return frameWriter.maxFrameSize(); + } + /** * Called when the write for any chunk fails. Fails all promises including * the one returned to the caller. diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java index c03d3707b3..01ccde15e5 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java @@ -15,12 +15,13 @@ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_LENGTH_MASK; import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_PAYLOAD_LENGTH; import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH; +import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_FRAME_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH; +import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid; import static io.netty.handler.codec.http2.Http2CodecUtil.readUnsignedInt; import static io.netty.handler.codec.http2.Http2Exception.protocolError; import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION; @@ -56,6 +57,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { private Http2Flags flags; private int payloadLength; private HeadersContinuation headersContinuation; + private int maxFrameSize = DEFAULT_MAX_FRAME_SIZE; public DefaultHttp2FrameReader() { this(new DefaultHttp2HeadersDecoder()); @@ -75,6 +77,29 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { return headersDecoder.maxHeaderTableSize(); } + @Override + public void maxFrameSize(int max) { + if (!isMaxFrameSizeValid(max)) { + throw new IllegalArgumentException("maxFrameSize is invalid: " + max); + } + maxFrameSize = max; + } + + @Override + public int maxFrameSize() { + return maxFrameSize; + } + + @Override + public void maxHeaderListSize(int max) { + headersDecoder.maxHeaderListSize(max); + } + + @Override + public int maxHeaderListSize() { + return headersDecoder.maxHeaderListSize(); + } + @Override public void close() { if (headersContinuation != null) { @@ -133,7 +158,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { } // Read the header and prepare the unmarshaller to read the frame. - payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK; + payloadLength = in.readUnsignedMedium(); + if (payloadLength > maxFrameSize) { + throw protocolError("Frame length: %d exceeds maximum: %d", payloadLength, maxFrameSize); + } frameType = in.readByte(); flags = new Http2Flags(in.readUnsignedByte()); streamId = readUnsignedInt(in); @@ -351,8 +379,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { } ByteBuf data = payload.readSlice(dataLength); - observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream(), - flags.endOfSegment()); + observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream()); payload.skipBytes(payload.readableBytes()); } @@ -385,8 +412,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { if (endOfHeaders) { Http2Headers headers = builder().buildHeaders(); observer.onHeadersRead(ctx, headersStreamId, headers, streamDependency, - weight, exclusive, padding, headersFlags.endOfStream(), - headersFlags.endOfSegment()); + weight, exclusive, padding, headersFlags.endOfStream()); close(); } } @@ -412,7 +438,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { if (endOfHeaders) { Http2Headers headers = builder().buildHeaders(); observer.onHeadersRead(ctx, headersStreamId, headers, padding, - headersFlags.endOfStream(), headersFlags.endOfSegment()); + headersFlags.endOfStream()); close(); } } @@ -444,11 +470,19 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { observer.onSettingsAckRead(ctx); } else { int numSettings = payloadLength / SETTING_ENTRY_LENGTH; - Http2Settings settings = new Http2Settings(5); + Http2Settings settings = new Http2Settings(); for (int index = 0; index < numSettings; ++index) { int id = payload.readUnsignedShort(); long value = payload.readUnsignedInt(); - settings.put(id, value); + try { + settings.put(id, value); + } catch (IllegalArgumentException e) { + if (id == SETTINGS_MAX_FRAME_SIZE) { + throw new Http2Exception(Http2Error.FRAME_SIZE_ERROR, e.getMessage(), e); + } else { + throw new Http2Exception(Http2Error.PROTOCOL_ERROR, e.getMessage(), e); + } + } } observer.onSettingsRead(ctx, settings); // Provide an interface for non-observers to capture settings @@ -643,16 +677,16 @@ public class DefaultHttp2FrameReader implements Http2FrameReader { } } + private void verifyPayloadLength(int payloadLength) throws Http2Exception { + if (payloadLength > maxFrameSize) { + throw protocolError("Total payload length %d exceeds max frame length.", payloadLength); + } + } + private static void verifyStreamOrConnectionId(int streamId, String argumentName) throws Http2Exception { if (streamId < 0) { throw protocolError("%s must be >= 0", argumentName); } } - - private static void verifyPayloadLength(int payloadLength) throws Http2Exception { - if (payloadLength > MAX_FRAME_PAYLOAD_LENGTH) { - throw protocolError("Total payload length %d exceeds max frame length.", payloadLength); - } - } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java index 01e0ade1ac..4e2fdc5945 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java @@ -15,15 +15,16 @@ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH; import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_PAYLOAD_LENGTH; import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE; import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT; import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT; import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT; import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH; import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH; +import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid; import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeader; import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt; import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort; @@ -50,6 +51,7 @@ import io.netty.util.collection.IntObjectMap; public class DefaultHttp2FrameWriter implements Http2FrameWriter { private final Http2HeadersEncoder headersEncoder; + private int maxFrameSize = DEFAULT_MAX_FRAME_SIZE; public DefaultHttp2FrameWriter() { this(new DefaultHttp2HeadersEncoder()); @@ -69,6 +71,29 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { return headersEncoder.maxHeaderTableSize(); } + @Override + public void maxFrameSize(int max) { + if (!isMaxFrameSizeValid(max)) { + throw new IllegalArgumentException("maxFrameSize is invalid: " + max); + } + maxFrameSize = max; + } + + @Override + public int maxFrameSize() { + return maxFrameSize; + } + + @Override + public void maxHeaderListSize(int max) { + headersEncoder.maxHeaderListSize(max); + } + + @Override + public int maxHeaderListSize() { + return headersEncoder.maxHeaderListSize(); + } + @Override public void close() { // Nothing to do. @@ -76,14 +101,12 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { @Override public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, - ByteBuf data, int padding, boolean endStream, boolean endSegment) { + ByteBuf data, int padding, boolean endStream) { try { verifyStreamId(streamId, "Stream ID"); verifyPadding(padding); - Http2Flags flags = - new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream) - .endOfSegment(endSegment); + Http2Flags flags = new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream); int payloadLength = data.readableBytes() + padding + flags.getPaddingPresenceFieldLength(); verifyPayloadLength(payloadLength); @@ -109,17 +132,17 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { @Override public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, - int streamId, Http2Headers headers, int padding, boolean endStream, boolean endSegment) { + int streamId, Http2Headers headers, int padding, boolean endStream) { return writeHeadersInternal(ctx, promise, streamId, headers, padding, endStream, - endSegment, false, 0, (short) 0, false); + false, 0, (short) 0, false); } @Override public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, Http2Headers headers, int streamDependency, short weight, - boolean exclusive, int padding, boolean endStream, boolean endSegment) { + boolean exclusive, int padding, boolean endStream) { return writeHeadersInternal(ctx, promise, streamId, headers, padding, endStream, - endSegment, true, streamDependency, weight, exclusive); + true, streamDependency, weight, exclusive); } @Override @@ -227,7 +250,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { Http2Flags flags = new Http2Flags().paddingPresent(padding > 0); int promisedStreamIdLength = INT_FIELD_LENGTH; int nonFragmentLength = promisedStreamIdLength + padding + flags.getPaddingPresenceFieldLength(); - int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength; + int maxFragmentLength = maxFrameSize - nonFragmentLength; ByteBuf fragment = headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); @@ -317,7 +340,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { } private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx, ChannelPromise promise, - int streamId, Http2Headers headers, int padding, boolean endStream, boolean endSegment, + int streamId, Http2Headers headers, int padding, boolean endStream, boolean hasPriority, int streamDependency, short weight, boolean exclusive) { ByteBuf headerBlock = null; try { @@ -333,13 +356,12 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { headersEncoder.encodeHeaders(headers, headerBlock); Http2Flags flags = - new Http2Flags().endOfStream(endStream).endOfSegment(endSegment) - .priorityPresent(hasPriority).paddingPresent(padding > 0); + new Http2Flags().endOfStream(endStream).priorityPresent(hasPriority).paddingPresent(padding > 0); // Read the first fragment (possibly everything). int nonFragmentBytes = padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength(); - int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentBytes; + int maxFragmentLength = maxFrameSize - nonFragmentBytes; ByteBuf fragment = headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); @@ -388,7 +410,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { * Drains the header block and creates a composite buffer containing the first frame and a * number of CONTINUATION frames. */ - private static ChannelFuture continueHeaders(ChannelHandlerContext ctx, ChannelPromise promise, + private ChannelFuture continueHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, int padding, ByteBuf headerBlock, ByteBuf firstFrame) { // Create a composite buffer wrapping the first frame and any continuation frames. CompositeByteBuf out = ctx.alloc().compositeBuffer(); @@ -410,11 +432,11 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { * Allocates a new buffer and writes a single continuation frame with a fragment of the header * block to the output buffer. */ - private static ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId, + private ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId, ByteBuf headerBlock, int padding) { Http2Flags flags = new Http2Flags().paddingPresent(padding > 0); int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength(); - int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength; + int maxFragmentLength = maxFrameSize - nonFragmentLength; ByteBuf fragment = headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); @@ -466,8 +488,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter { } } - private static void verifyPayloadLength(int payloadLength) { - if (payloadLength > MAX_FRAME_PAYLOAD_LENGTH) { + private void verifyPayloadLength(int payloadLength) { + if (payloadLength > maxFrameSize) { throw new IllegalArgumentException("Total payload length " + payloadLength + " exceeds max frame length."); } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java index a3861cc489..8789eb49f6 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java @@ -15,6 +15,10 @@ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.AUTHORITY; +import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.METHOD; +import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.SCHEME; + import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -33,10 +37,12 @@ public final class DefaultHttp2Headers extends Http2Headers { private final HeaderEntry[] entries; private final HeaderEntry head; + private final int size; private DefaultHttp2Headers(Builder builder) { entries = builder.entries; head = builder.head; + size = builder.size; } @Override @@ -97,7 +103,12 @@ public final class DefaultHttp2Headers extends Http2Headers { @Override public boolean isEmpty() { - return head == head.after; + return size == 0; + } + + @Override + public int size() { + return size; } @Override @@ -131,11 +142,16 @@ public final class DefaultHttp2Headers extends Http2Headers { private HeaderEntry[] entries; private HeaderEntry head; private Http2Headers buildResults; + private int size; public Builder() { clear(); } + /** + * Clears all existing headers from this collection and replaces them with the given header + * set. + */ public void set(Http2Headers headers) { // No need to lazy copy the previous results, since we're starting from scratch. clear(); @@ -144,6 +160,11 @@ public final class DefaultHttp2Headers extends Http2Headers { } } + /** + * Adds the given header to the collection. + * + * @throws IllegalArgumentException if the name or value of this header is invalid for any reason. + */ public Builder add(final String name, final Object value) { // If this is the first call on the builder since the last build, copy the previous // results. @@ -159,6 +180,9 @@ public final class DefaultHttp2Headers extends Http2Headers { return this; } + /** + * Removes the header with the given name from this collection. + */ public Builder remove(final String name) { if (name == null) { throw new NullPointerException("name"); @@ -175,6 +199,11 @@ public final class DefaultHttp2Headers extends Http2Headers { return this; } + /** + * Sets the given header in the collection, replacing any previous values. + * + * @throws IllegalArgumentException if the name or value of this header is invalid for any reason. + */ public Builder set(final String name, final Object value) { // If this is the first call on the builder since the last build, copy the previous // results. @@ -191,6 +220,11 @@ public final class DefaultHttp2Headers extends Http2Headers { return this; } + /** + * Sets the given header in the collection, replacing any previous values. + * + * @throws IllegalArgumentException if the name or value of this header is invalid for any reason. + */ public Builder set(final String name, final Iterable values) { if (values == null) { throw new NullPointerException("values"); @@ -218,48 +252,52 @@ public final class DefaultHttp2Headers extends Http2Headers { return this; } + /** + * Clears all values from this collection. + */ public Builder clear() { // No lazy copy required, since we're just creating a new array. entries = new HeaderEntry[BUCKET_SIZE]; head = new HeaderEntry(-1, null, null); head.before = head.after = head; buildResults = null; + size = 0; return this; } /** - * Sets the {@link HttpName#METHOD} header. + * Sets the {@link PseudoHeaderName#METHOD} header. */ public Builder method(String method) { - return set(HttpName.METHOD.value(), method); + return set(METHOD.value(), method); } /** - * Sets the {@link HttpName#SCHEME} header. + * Sets the {@link PseudoHeaderName#SCHEME} header. */ public Builder scheme(String scheme) { - return set(HttpName.SCHEME.value(), scheme); + return set(SCHEME.value(), scheme); } /** - * Sets the {@link HttpName#AUTHORITY} header. + * Sets the {@link PseudoHeaderName#AUTHORITY} header. */ public Builder authority(String authority) { - return set(HttpName.AUTHORITY.value(), authority); + return set(AUTHORITY.value(), authority); } /** - * Sets the {@link HttpName#PATH} header. + * Sets the {@link PseudoHeaderName#PATH} header. */ public Builder path(String path) { - return set(HttpName.PATH.value(), path); + return set(PseudoHeaderName.PATH.value(), path); } /** - * Sets the {@link HttpName#STATUS} header. + * Sets the {@link PseudoHeaderName#STATUS} header. */ public Builder status(String status) { - return set(HttpName.STATUS.value(), status); + return set(PseudoHeaderName.STATUS.value(), status); } /** @@ -299,6 +337,7 @@ public final class DefaultHttp2Headers extends Http2Headers { // Update the linked list. newEntry.addBefore(head); + size++; } private void remove0(int hash, int hashTableIndex, String name) { @@ -310,6 +349,7 @@ public final class DefaultHttp2Headers extends Http2Headers { for (;;) { if (e.hash == hash && eq(name, e.key)) { e.remove(); + size--; HeaderEntry next = e.next; if (next != null) { entries[hashTableIndex] = next; @@ -331,6 +371,7 @@ public final class DefaultHttp2Headers extends Http2Headers { if (next.hash == hash && eq(name, next.key)) { e.next = next.next; next.remove(); + size--; } else { e = next; } @@ -360,7 +401,7 @@ public final class DefaultHttp2Headers extends Http2Headers { } /** - * Validate a HTTP2 header name. + * Validate a HTTP/2 header name. */ private static void validateHeaderName(String name) { if (name == null) { @@ -383,6 +424,13 @@ public final class DefaultHttp2Headers extends Http2Headers { throw new IllegalArgumentException("name contains non-ascii character: " + name); } } + // If the name looks like an HTTP/2 pseudo-header, validate it against the list of + // valid pseudo-headers. + if (name.startsWith(PSEUDO_HEADER_PREFIX)) { + if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) { + throw new IllegalArgumentException("Invalid HTTP/2 Pseudo-header: " + name); + } + } } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java index 2720d5254f..6726c7c22f 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java @@ -18,6 +18,7 @@ package io.netty.handler.codec.http2; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_HEADER_SIZE; import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR; +import static io.netty.handler.codec.http2.Http2Exception.protocolError; import static io.netty.util.CharsetUtil.UTF_8; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; @@ -30,6 +31,7 @@ import com.twitter.hpack.HeaderListener; public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder { private final Decoder decoder; + private int maxHeaderListSize = Integer.MAX_VALUE; public DefaultHttp2HeadersDecoder() { this(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE); @@ -49,26 +51,52 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder { return decoder.getMaxHeaderTableSize(); } + @Override + public void maxHeaderListSize(int max) { + if (max < 0) { + throw new IllegalArgumentException("maxHeaderListSize must be >= 0: " + max); + } + maxHeaderListSize = max; + } + + @Override + public int maxHeaderListSize() { + return maxHeaderListSize; + } + @Override public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception { try { final DefaultHttp2Headers.Builder headersBuilder = new DefaultHttp2Headers.Builder(); HeaderListener listener = new HeaderListener() { @Override - public void emitHeader(byte[] key, byte[] value, boolean sensitive) { - headersBuilder.add(new String(key, UTF_8), new String(value, UTF_8)); + public void addHeader(byte[] key, byte[] value, boolean sensitive) { + String keyString = new String(key, UTF_8); + String valueString = new String(value, UTF_8); + headersBuilder.add(keyString, valueString); } }; decoder.decode(new ByteBufInputStream(headerBlock), listener); - boolean truncated = decoder.endHeaderBlock(listener); + boolean truncated = decoder.endHeaderBlock(); if (truncated) { // TODO: what's the right thing to do here? } - return headersBuilder.build(); + Http2Headers headers = headersBuilder.build(); + if (headers.size() > maxHeaderListSize) { + throw protocolError("Number of headers (%d) exceeds maxHeaderListSize (%d)", + headers.size(), maxHeaderListSize); + } + + return headers; } catch (IOException e) { throw new Http2Exception(COMPRESSION_ERROR, e.getMessage()); + } catch (Throwable e) { + // Default handler for any other types of errors that may have occurred. For example, + // the the Header builder throws IllegalArgumentException if the key or value was invalid + // for any reason (e.g. the key was an invalid pseudo-header). + throw new Http2Exception(Http2Error.PROTOCOL_ERROR, e.getMessage(), e); } } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java index d60bbb80df..83ca377c83 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java @@ -16,6 +16,8 @@ package io.netty.handler.codec.http2; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE; +import static io.netty.handler.codec.http2.Http2Exception.protocolError; +import static io.netty.handler.codec.http2.Http2Headers.PSEUDO_HEADER_PREFIX; import static io.netty.util.CharsetUtil.UTF_8; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; @@ -23,25 +25,36 @@ import io.netty.buffer.Unpooled; import java.io.IOException; import java.io.OutputStream; +import java.util.Collections; import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; import com.twitter.hpack.Encoder; public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder { private final Encoder encoder; private final ByteBuf tableSizeChangeOutput = Unpooled.buffer(); + private final Set sensitiveHeaders = new TreeSet(String.CASE_INSENSITIVE_ORDER); + private int maxHeaderListSize = Integer.MAX_VALUE; public DefaultHttp2HeadersEncoder() { - this(DEFAULT_HEADER_TABLE_SIZE); + this(DEFAULT_HEADER_TABLE_SIZE, Collections.emptySet()); } - public DefaultHttp2HeadersEncoder(int maxHeaderTableSize) { + public DefaultHttp2HeadersEncoder(int maxHeaderTableSize, Set sensitiveHeaders) { encoder = new Encoder(maxHeaderTableSize); + this.sensitiveHeaders.addAll(sensitiveHeaders); } @Override public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception { try { + if (headers.size() > maxHeaderListSize) { + throw protocolError("Number of headers (%d) exceeds maxHeaderListSize (%d)", + headers.size(), maxHeaderListSize); + } + // If there was a change in the table size, serialize the output from the encoder // resulting from that change. if (tableSizeChangeOutput.isReadable()) { @@ -50,12 +63,19 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder { } OutputStream stream = new ByteBufOutputStream(buffer); - for (Entry header : headers) { - byte[] key = header.getKey().getBytes(UTF_8); - byte[] value = header.getValue().getBytes(UTF_8); - encoder.encodeHeader(stream, key, value, false); + // Write pseudo headers first as required by the HTTP/2 spec. + for (Http2Headers.PseudoHeaderName pseudoHeader : Http2Headers.PseudoHeaderName.values()) { + String name = pseudoHeader.value(); + String value = headers.get(name); + if (value != null) { + encodeHeader(name, value, stream); + } + } + for (Entry header : headers) { + if (!header.getKey().startsWith(PSEUDO_HEADER_PREFIX)) { + encodeHeader(header.getKey(), header.getValue(), stream); + } } - encoder.endHeaders(stream); } catch (IOException e) { throw Http2Exception.format(Http2Error.COMPRESSION_ERROR, "Failed encoding headers block: %s", e.getMessage()); @@ -77,4 +97,21 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder { return encoder.getMaxHeaderTableSize(); } + @Override + public void maxHeaderListSize(int max) { + if (max < 0) { + throw new IllegalArgumentException("maxHeaderListSize must be positive: " + max); + } + maxHeaderListSize = max; + } + + @Override + public int maxHeaderListSize() { + return maxHeaderListSize; + } + + private void encodeHeader(String key, String value, OutputStream stream) throws IOException { + boolean sensitive = sensitiveHeaders.contains(key); + encoder.encodeHeader(stream, key.getBytes(UTF_8), value.getBytes(UTF_8), sensitive); + } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowController.java index 413ab442ab..83bb670b11 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowController.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowController.java @@ -66,7 +66,7 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro @Override public void applyInboundFlowControl(int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment, FrameWriter frameWriter) + boolean endOfStream, FrameWriter frameWriter) throws Http2Exception { int dataLength = data.readableBytes(); applyConnectionFlowControl(dataLength, frameWriter); diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowController.java index b731c5817a..ec8a67b685 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowController.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowController.java @@ -149,13 +149,16 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont @Override public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream, - boolean endSegment, FrameWriter frameWriter) throws Http2Exception { + FrameWriter frameWriter) throws Http2Exception { OutboundFlowState state = stateOrFail(streamId); OutboundFlowState.Frame frame = - state.newFrame(data, padding, endStream, endSegment, frameWriter); + state.newFrame(data, padding, endStream, frameWriter); + + // Limit the window for this write by the maximum frame size. + int window = state.writableWindow(); int dataLength = data.readableBytes(); - if (state.writableWindow() >= dataLength) { + if (window >= dataLength) { // Window size is large enough to send entire data frame frame.write(); return; @@ -164,13 +167,13 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont // Enqueue the frame to be written when the window size permits. frame.enqueue(); - if (state.writableWindow() <= 0) { + if (window <= 0) { // Stream is stalled, don't send anything now. return; } // Create and send a partial frame up to the window size. - frame.split(state.writableWindow()).write(); + frame.split(window).write(); } private static OutboundFlowState state(Http2Stream stream) { @@ -441,9 +444,8 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont /** * Creates a new frame with the given values but does not add it to the pending queue. */ - Frame newFrame(ByteBuf data, int padding, boolean endStream, boolean endSegment, - FrameWriter writer) { - return new Frame(data, padding, endStream, endSegment, writer); + Frame newFrame(ByteBuf data, int padding, boolean endStream, FrameWriter writer) { + return new Frame(data, padding, endStream, writer); } /** @@ -529,16 +531,13 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont private final ByteBuf data; private final int padding; private final boolean endStream; - private final boolean endSegment; private final FrameWriter writer; private boolean enqueued; - Frame(ByteBuf data, int padding, boolean endStream, boolean endSegment, - FrameWriter writer) { + Frame(ByteBuf data, int padding, boolean endStream, FrameWriter writer) { this.data = data; this.padding = padding; this.endStream = endStream; - this.endSegment = endSegment; this.writer = writer; } @@ -575,11 +574,25 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont * priority tree. */ void write() throws Http2Exception { - int dataLength = data.readableBytes(); - connectionState().incrementStreamWindow(-dataLength); - incrementStreamWindow(-dataLength); - writer.writeFrame(stream.id(), data, padding, endStream, endSegment); - decrementPendingBytes(dataLength); + // Using a do/while loop because if the buffer is empty we still need to call + // the writer once to send the empty frame. + do { + int bytesToWrite = data.readableBytes(); + int frameBytes = Math.min(bytesToWrite, writer.maxFrameSize()); + if (frameBytes == bytesToWrite) { + // All the bytes fit into a single HTTP/2 frame, just send it all. + connectionState().incrementStreamWindow(-bytesToWrite); + incrementStreamWindow(-bytesToWrite); + ByteBuf slice = data.readSlice(bytesToWrite); + writer.writeFrame(stream.id(), slice, padding, endStream); + decrementPendingBytes(bytesToWrite); + return; + } + + // Split a chunk that will fit into a single HTTP/2 frame and write it. + Frame frame = split(frameBytes); + frame.write(); + } while (data.isReadable()); } /** @@ -604,7 +617,7 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont Frame split(int maxBytes) { // TODO: Should padding be included in the chunks or only the last frame? maxBytes = min(maxBytes, data.readableBytes()); - Frame frame = new Frame(data.readSlice(maxBytes).retain(), 0, false, false, writer); + Frame frame = new Frame(data.readSlice(maxBytes).retain(), 0, false, writer); decrementPendingBytes(maxBytes); return frame; } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandler.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandler.java index a66f4b797a..6105e54876 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandler.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandler.java @@ -53,22 +53,22 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan @Override public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, - ByteBuf data, int padding, boolean endStream, boolean endSegment) { - return super.writeData(ctx, promise, streamId, data, padding, endStream, endSegment); + ByteBuf data, int padding, boolean endStream) { + return super.writeData(ctx, promise, streamId, data, padding, endStream); } @Override public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, - int streamId, Http2Headers headers, int padding, boolean endStream, boolean endSegment) { - return super.writeHeaders(ctx, promise, streamId, headers, padding, endStream, endSegment); + int streamId, Http2Headers headers, int padding, boolean endStream) { + return super.writeHeaders(ctx, promise, streamId, headers, padding, endStream); } @Override public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, Http2Headers headers, int streamDependency, short weight, - boolean exclusive, int padding, boolean endStream, boolean endSegment) { + boolean exclusive, int padding, boolean endStream) { return super.writeHeaders(ctx, promise, streamId, headers, streamDependency, weight, - exclusive, padding, endStream, endSegment); + exclusive, padding, endStream); } @Override @@ -102,16 +102,16 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) throws Http2Exception { - observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment); + boolean endOfStream) throws Http2Exception { + observer.onDataRead(ctx, streamId, data, padding, endOfStream); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, - boolean endSegment) throws Http2Exception { + int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) + throws Http2Exception { observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, - padding, endStream, endSegment); + padding, endStream); } @Override diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandler.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandler.java index baa26b8681..78f2148cb2 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandler.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandler.java @@ -177,10 +177,10 @@ public class DelegatingHttp2HttpConnectionHandler extends DelegatingHttp2Connect ChannelPromise headerPromise = ctx.newPromise(); ChannelPromise dataPromise = ctx.newPromise(); promiseAggregator.add(headerPromise, dataPromise); - writeHeaders(ctx, headerPromise, streamId, http2Headers.build(), 0, false, false); - writeData(ctx, dataPromise, streamId, httpMsg.content(), 0, true, true); + writeHeaders(ctx, headerPromise, streamId, http2Headers.build(), 0, false); + writeData(ctx, dataPromise, streamId, httpMsg.content(), 0, true); } else { - writeHeaders(ctx, promise, streamId, http2Headers.build(), 0, true, true); + writeHeaders(ctx, promise, streamId, http2Headers.build(), 0, true); } } else { ctx.write(msg, promise); diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java index f7e409605f..edbc5540eb 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java @@ -20,6 +20,8 @@ import static io.netty.handler.codec.http2.Http2Exception.format; import static io.netty.util.CharsetUtil.UTF_8; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action; @@ -31,19 +33,19 @@ public final class Http2CodecUtil { private static final byte[] CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(UTF_8); private static final byte[] EMPTY_PING = new byte[8]; + private static IgnoreSettingsHandler ignoreSettingsHandler = new IgnoreSettingsHandler(); public static final int CONNECTION_STREAM_ID = 0; public static final int HTTP_UPGRADE_STREAM_ID = 1; public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings"; - public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-13"; + public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-14"; + public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-14"; - public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383; public static final int PING_FRAME_PAYLOAD_LENGTH = 8; public static final short MAX_UNSIGNED_BYTE = 0xFF; public static final int MAX_UNSIGNED_SHORT = 0xFFFF; public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL; - public static final int FRAME_HEADER_LENGTH = 8; - public static final int FRAME_LENGTH_MASK = 0x3FFF; + public static final int FRAME_HEADER_LENGTH = 9; public static final int SETTING_ENTRY_LENGTH = 6; public static final int PRIORITY_ENTRY_LENGTH = 5; public static final int INT_FIELD_LENGTH = 4; @@ -54,12 +56,26 @@ public final class Http2CodecUtil { public static final int SETTINGS_ENABLE_PUSH = 2; public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3; public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4; + public static final int SETTINGS_MAX_FRAME_SIZE = 5; + public static final int SETTINGS_MAX_HEADER_LIST_SIZE = 6; + + public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000; + public static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xFFFFFF; public static final int DEFAULT_WINDOW_SIZE = 65535; public static final boolean DEFAULT_ENABLE_PUSH = true; public static final short DEFAULT_PRIORITY_WEIGHT = 16; public static final int DEFAULT_HEADER_TABLE_SIZE = 4096; public static final int DEFAULT_MAX_HEADER_SIZE = 8192; + public static final int DEFAULT_MAX_FRAME_SIZE = MAX_FRAME_SIZE_LOWER_BOUND; + + /** + * Indicates whether or not the given value for max frame size falls within the valid range. + */ + public static boolean isMaxFrameSizeValid(int maxFrameSize) { + return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND + && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND; + } /** * Returns a buffer containing the the {@link #CONNECTION_PREFACE}. @@ -106,6 +122,15 @@ public final class Http2CodecUtil { }; } + /** + * Creates a new {@link ChannelHandler} that does nothing but ignore inbound settings frames. + * This is a useful utility to avoid verbose logging output for pipelines that don't handle + * settings frames directly. + */ + public static ChannelHandler ignoreSettingsHandler() { + return ignoreSettingsHandler; + } + /** * Converts the given cause to a {@link Http2Exception} if it isn't already. */ @@ -165,7 +190,7 @@ public final class Http2CodecUtil { public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type, Http2Flags flags, int streamId) { out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength); - out.writeShort(payloadLength); + out.writeMedium(payloadLength); out.writeByte(type); out.writeByte(flags.value()); out.writeInt(streamId); @@ -181,6 +206,21 @@ public final class Http2CodecUtil { throw cause; } + /** + * A{@link ChannelHandler} that does nothing but ignore inbound settings frames. This is a + * useful utility to avoid verbose logging output for pipelines that don't handle settings + * frames directly. + */ + @ChannelHandler.Sharable + private static class IgnoreSettingsHandler extends ChannelHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!(msg instanceof Http2Settings)) { + super.channelRead(ctx, msg); + } + } + } + private Http2CodecUtil() { } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Flags.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Flags.java index 8540ec83c0..c3dd8a44ff 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Flags.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Flags.java @@ -20,7 +20,6 @@ package io.netty.handler.codec.http2; */ public final class Http2Flags { public static final short END_STREAM = 0x1; - public static final short END_SEGMENT = 0x2; public static final short END_HEADERS = 0x4; public static final short ACK = 0x1; public static final short PADDED = 0x8; @@ -50,14 +49,6 @@ public final class Http2Flags { return isFlagSet(END_STREAM); } - /** - * Determines whether the {@link #END_SEGMENT} flag is set. Only applies to DATA and HEADERS - * frames. - */ - public boolean endOfSegment() { - return isFlagSet(END_SEGMENT); - } - /** * Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS, * PUSH_PROMISE, and CONTINUATION frames. @@ -113,13 +104,6 @@ public final class Http2Flags { return setFlag(endOfStream, END_STREAM); } - /** - * Sets the {@link #END_SEGMENT} flag. - */ - public Http2Flags endOfSegment(boolean endOfSegment) { - return setFlag(endOfSegment, END_SEGMENT); - } - /** * Sets the {@link #END_HEADERS} flag. */ @@ -211,9 +195,6 @@ public final class Http2Flags { if (priorityPresent()) { builder.append("PRIORITY_PRESENT,"); } - if (endOfSegment()) { - builder.append("END_OF_SEGMENT,"); - } if (paddingPresent()) { builder.append("PADDING_PRESENT,"); } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameAdapter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameAdapter.java index a16daee2c2..ff129c6895 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameAdapter.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameAdapter.java @@ -24,18 +24,18 @@ public class Http2FrameAdapter implements Http2FrameObserver { @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) throws Http2Exception { + boolean endOfStream) throws Http2Exception { } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int padding, boolean endStream, boolean endSegment) throws Http2Exception { + int padding, boolean endStream) throws Http2Exception { } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, - boolean endSegment) throws Http2Exception { + int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) + throws Http2Exception { } @Override diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameLogger.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameLogger.java index e4328e571c..f6e75c874d 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameLogger.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameLogger.java @@ -51,25 +51,24 @@ public class Http2FrameLogger extends ChannelHandlerAdapter { } public void logData(Direction direction, int streamId, ByteBuf data, int padding, - boolean endStream, boolean endSegment) { + boolean endStream) { log(direction, - "DATA: streamId=%d, padding=%d, endStream=%b, endSegment=%b, length=%d, bytes=%s", - streamId, padding, endStream, endSegment, data.readableBytes(), ByteBufUtil.hexDump(data)); + "DATA: streamId=%d, padding=%d, endStream=%b, length=%d, bytes=%s", + streamId, padding, endStream, data.readableBytes(), ByteBufUtil.hexDump(data)); } public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding, - boolean endStream, boolean endSegment) { - log(direction, "HEADERS: streamId:%d, headers=%s, padding=%d, endStream=%b, endSegment=%b", - streamId, headers, padding, endStream, endSegment); + boolean endStream) { + log(direction, "HEADERS: streamId:%d, headers=%s, padding=%d, endStream=%b", + streamId, headers, padding, endStream); } public void logHeaders(Direction direction, int streamId, Http2Headers headers, - int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, - boolean endSegment) { + int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) { log(direction, "HEADERS: streamId:%d, headers=%s, streamDependency=%d, weight=%d, exclusive=%b, " - + "padding=%d, endStream=%b, endSegment=%b", streamId, headers, - streamDependency, weight, exclusive, padding, endStream, endSegment); + + "padding=%d, endStream=%b", streamId, headers, + streamDependency, weight, exclusive, padding, endStream); } public void logPriority(Direction direction, int streamId, int streamDependency, short weight, diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameObserver.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameObserver.java index 0f5fc1c573..4f1f877fd4 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameObserver.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameObserver.java @@ -33,10 +33,9 @@ public interface Http2FrameObserver { * @param padding the number of padding bytes found at the end of the frame. * @param endOfStream Indicates whether this is the last frame to be sent from the remote * endpoint for this stream. - * @param endOfSegment Indicates whether this frame is the end of the current segment. */ void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) throws Http2Exception; + boolean endOfStream) throws Http2Exception; /** * Handles an inbound HEADERS frame. @@ -47,10 +46,9 @@ public interface Http2FrameObserver { * @param padding the number of padding bytes found at the end of the frame. * @param endStream Indicates whether this is the last frame to be sent from the remote endpoint * for this stream. - * @param endSegment Indicates whether this frame is the end of the current segment. */ void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, - boolean endStream, boolean endSegment) throws Http2Exception; + boolean endStream) throws Http2Exception; /** * Handles an inbound HEADERS frame with priority information specified. @@ -65,11 +63,10 @@ public interface Http2FrameObserver { * @param padding the number of padding bytes found at the end of the frame. * @param endStream Indicates whether this is the last frame to be sent from the remote endpoint * for this stream. - * @param endSegment Indicates whether this frame is the end of the current segment. */ void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, - boolean endSegment) throws Http2Exception; + int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) + throws Http2Exception; /** * Handles an inbound PRIORITY frame. diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameReader.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameReader.java index 7deecc3dc1..414c52c291 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameReader.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameReader.java @@ -43,6 +43,26 @@ public interface Http2FrameReader extends Closeable { */ long maxHeaderTableSize(); + /** + * Sets the maximum allowed frame size. Attempts to read frames longer than this maximum will fail. + */ + void maxFrameSize(int max); + + /** + * Gets the maximum allowed frame size. + */ + int maxFrameSize(); + + /** + * Sets the maximum allowed header elements. + */ + void maxHeaderListSize(int max); + + /** + * Gets the maximum allowed header elements. + */ + int maxHeaderListSize(); + /** * Closes this reader and frees any allocated resources. */ diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameWriter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameWriter.java index cab8fa3f0c..07067e9b82 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameWriter.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameWriter.java @@ -36,11 +36,10 @@ public interface Http2FrameWriter extends Closeable { * @param data the payload of the frame. * @param padding the amount of padding to be added to the end of the frame * @param endStream indicates if this is the last frame to be sent for the stream. - * @param endSegment indicates if this is the last frame in the current segment. * @return the future for the write. */ ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, - ByteBuf data, int padding, boolean endStream, boolean endSegment); + ByteBuf data, int padding, boolean endStream); /** * Writes a HEADERS frame to the remote endpoint. @@ -51,11 +50,10 @@ public interface Http2FrameWriter extends Closeable { * @param headers the headers to be sent. * @param padding the amount of padding to be added to the end of the frame * @param endStream indicates if this is the last frame to be sent for the stream. - * @param endSegment indicates if this is the last frame in the current segment. * @return the future for the write. */ ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, - Http2Headers headers, int padding, boolean endStream, boolean endSegment); + Http2Headers headers, int padding, boolean endStream); /** * Writes a HEADERS frame with priority specified to the remote endpoint. @@ -70,12 +68,11 @@ public interface Http2FrameWriter extends Closeable { * @param exclusive whether this stream should be the exclusive dependant of its parent. * @param padding the amount of padding to be added to the end of the frame * @param endStream indicates if this is the last frame to be sent for the stream. - * @param endSegment indicates if this is the last frame in the current segment. * @return the future for the write. */ ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, - int padding, boolean endStream, boolean endSegment); + int padding, boolean endStream); /** * Writes a PRIORITY frame to the remote endpoint. @@ -206,4 +203,24 @@ public interface Http2FrameWriter extends Closeable { * Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers. */ long maxHeaderTableSize(); + + /** + * Sets the maximum allowed frame size. Attempts to write frames longer than this maximum will fail. + */ + void maxFrameSize(int max); + + /** + * Gets the maximum allowed frame size. + */ + int maxFrameSize(); + + /** + * Sets the maximum allowed header elements. + */ + void maxHeaderListSize(int max); + + /** + * Gets the maximum allowed header elements. + */ + int maxHeaderListSize(); } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java index 2aa7a42a4f..da8514beab 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java @@ -56,6 +56,11 @@ public abstract class Http2Headers implements Iterable> { return true; } + @Override + public int size() { + return 0; + } + @Override public Set names() { return Collections.emptySet(); @@ -68,43 +73,68 @@ public abstract class Http2Headers implements Iterable> { }; /** - * HTTP2 header names. + * The prefix used to denote an HTTP/2 psuedo-header. */ - public enum HttpName { + public static String PSEUDO_HEADER_PREFIX = ":"; + + /** + * HTTP/2 pseudo-headers names. + */ + public enum PseudoHeaderName { /** * {@code :method}. */ - METHOD(":method"), + METHOD(PSEUDO_HEADER_PREFIX + "method"), /** * {@code :scheme}. */ - SCHEME(":scheme"), + SCHEME(PSEUDO_HEADER_PREFIX + "scheme"), /** * {@code :authority}. */ - AUTHORITY(":authority"), + AUTHORITY(PSEUDO_HEADER_PREFIX + "authority"), /** * {@code :path}. */ - PATH(":path"), + PATH(PSEUDO_HEADER_PREFIX + "path"), /** * {@code :status}. */ - STATUS(":status"); + STATUS(PSEUDO_HEADER_PREFIX + "status"); private final String value; - HttpName(String value) { + PseudoHeaderName(String value) { this.value = value; } public String value() { return value; } + + /** + * Indicates whether the given header name is a valid HTTP/2 pseudo header. + */ + public static boolean isPseudoHeader(String header) { + if (header == null || !header.startsWith(Http2Headers.PSEUDO_HEADER_PREFIX)) { + // Not a pseudo-header. + return false; + } + + // Check the header name against the set of valid pseudo-headers. + for (PseudoHeaderName pseudoHeader : PseudoHeaderName.values()) { + String pseudoHeaderName = pseudoHeader.value(); + if (pseudoHeaderName.equals(header)) { + // It's a valid pseudo-header. + return true; + } + } + return false; + } } /** @@ -146,38 +176,43 @@ public abstract class Http2Headers implements Iterable> { public abstract boolean isEmpty(); /** - * Gets the {@link HttpName#METHOD} header or {@code null} if there is no such header + * Gets the number of headers contained in this object. + */ + public abstract int size(); + + /** + * Gets the {@link PseudoHeaderName#METHOD} header or {@code null} if there is no such header */ public final String method() { - return get(HttpName.METHOD.value()); + return get(PseudoHeaderName.METHOD.value()); } /** - * Gets the {@link HttpName#SCHEME} header or {@code null} if there is no such header + * Gets the {@link PseudoHeaderName#SCHEME} header or {@code null} if there is no such header */ public final String scheme() { - return get(HttpName.SCHEME.value()); + return get(PseudoHeaderName.SCHEME.value()); } /** - * Gets the {@link HttpName#AUTHORITY} header or {@code null} if there is no such header + * Gets the {@link PseudoHeaderName#AUTHORITY} header or {@code null} if there is no such header */ public final String authority() { - return get(HttpName.AUTHORITY.value()); + return get(PseudoHeaderName.AUTHORITY.value()); } /** - * Gets the {@link HttpName#PATH} header or {@code null} if there is no such header + * Gets the {@link PseudoHeaderName#PATH} header or {@code null} if there is no such header */ public final String path() { - return get(HttpName.PATH.value()); + return get(PseudoHeaderName.PATH.value()); } /** - * Gets the {@link HttpName#STATUS} header or {@code null} if there is no such header + * Gets the {@link PseudoHeaderName#STATUS} header or {@code null} if there is no such header */ public final String status() { - return get(HttpName.STATUS.value()); + return get(PseudoHeaderName.STATUS.value()); } @Override diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersDecoder.java index b4a4e57d8c..3ad4b1f687 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersDecoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersDecoder.java @@ -36,4 +36,14 @@ public interface Http2HeadersDecoder { * Gets the maximum header table size for this decoder. */ int maxHeaderTableSize(); + + /** + * Sets the maximum allowed header elements. + */ + void maxHeaderListSize(int max); + + /** + * Gets the maximum allowed header elements. + */ + int maxHeaderListSize(); } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersEncoder.java index f7657a9030..60c6a625bd 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersEncoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2HeadersEncoder.java @@ -39,4 +39,14 @@ public interface Http2HeadersEncoder { * Gets the current maximum value for the header table size. */ int maxHeaderTableSize(); + + /** + * Sets the maximum allowed header elements. + */ + void maxHeaderListSize(int max); + + /** + * Gets the maximum allowed header elements. + */ + int maxHeaderListSize(); } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFlowController.java index 28a3375c57..9deafb2115 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFlowController.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFlowController.java @@ -54,10 +54,9 @@ public interface Http2InboundFlowController { * @param data the data portion of the data frame. Does not contain padding. * @param padding the amount of padding received in the original frame. * @param endOfStream indicates whether this is the last frame for the stream. - * @param endOfSegment indicates whether this is the last frame for the current segment. * @param frameWriter allows this flow controller to send window updates to the remote endpoint. * @throws Http2Exception thrown if any protocol-related error occurred. */ void applyInboundFlowControl(int streamId, ByteBuf data, int padding, boolean endOfStream, - boolean endOfSegment, FrameWriter frameWriter) throws Http2Exception; + FrameWriter frameWriter) throws Http2Exception; } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFrameLogger.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFrameLogger.java index 09f7b7191b..c7699555a6 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFrameLogger.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2InboundFrameLogger.java @@ -46,28 +46,28 @@ public class Http2InboundFrameLogger implements Http2FrameReader { @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, - int padding, boolean endOfStream, boolean endOfSegment) + int padding, boolean endOfStream) throws Http2Exception { - logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment); - observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment); + logger.logData(INBOUND, streamId, data, padding, endOfStream); + observer.onDataRead(ctx, streamId, data, padding, endOfStream); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, - Http2Headers headers, int padding, boolean endStream, boolean endSegment) + Http2Headers headers, int padding, boolean endStream) throws Http2Exception { - logger.logHeaders(INBOUND, streamId, headers, padding, endStream, endSegment); - observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); + logger.logHeaders(INBOUND, streamId, headers, padding, endStream); + observer.onHeadersRead(ctx, streamId, headers, padding, endStream); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, - int padding, boolean endStream, boolean endSegment) throws Http2Exception { + int padding, boolean endStream) throws Http2Exception { logger.logHeaders(INBOUND, streamId, headers, streamDependency, weight, exclusive, - padding, endStream, endSegment); + padding, endStream); observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, - padding, endStream, endSegment); + padding, endStream); } @Override @@ -154,4 +154,23 @@ public class Http2InboundFrameLogger implements Http2FrameReader { return reader.maxHeaderTableSize(); } + @Override + public void maxFrameSize(int max) { + reader.maxFrameSize(max); + } + + @Override + public int maxFrameSize() { + return reader.maxFrameSize(); + } + + @Override + public void maxHeaderListSize(int max) { + reader.maxHeaderListSize(max); + } + + @Override + public int maxHeaderListSize() { + return reader.maxHeaderListSize(); + } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java index aedfd3603a..99b9d72a8f 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java @@ -15,6 +15,7 @@ */ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http2.Http2CodecUtil.TLS_UPGRADE_PROTOCOL_NAME; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; @@ -40,7 +41,7 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder { public enum SelectedProtocol { /** Must be updated to match the HTTP/2 draft number. */ - HTTP_2("h2-13"), + HTTP_2(TLS_UPGRADE_PROTOCOL_NAME), HTTP_1_1("http/1.1"), HTTP_1_0("http/1.0"), UNKNOWN("Unknown"); diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFlowController.java index 4573e1eb0a..6629512d59 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFlowController.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFlowController.java @@ -30,14 +30,18 @@ public interface Http2OutboundFlowController { /** * Writes a single data frame to the remote endpoint. */ - void writeFrame(int streamId, ByteBuf data, int padding, boolean endStream, - boolean endSegment); + void writeFrame(int streamId, ByteBuf data, int padding, boolean endStream); /** * Called if an error occurred before the write could take place. Sets the failure on the * channel promise. */ void setFailure(Throwable cause); + + /** + * Gets the maximum allowed frame size. + */ + int maxFrameSize(); } /** @@ -79,10 +83,9 @@ public interface Http2OutboundFlowController { * @param data the data be be sent to the remote endpoint. * @param padding the number of bytes of padding to be added to the frame. * @param endStream indicates whether this frames is to be the last sent on this stream. - * @param endSegment indicates whether this is to be the last frame in the segment. * @param frameWriter peforms to the write of the frame to the remote endpoint. * @throws Http2Exception thrown if a protocol-related error occurred. */ void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream, - boolean endSegment, FrameWriter frameWriter) throws Http2Exception; + FrameWriter frameWriter) throws Http2Exception; } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFrameLogger.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFrameLogger.java index 83112168f1..911e6eaf74 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFrameLogger.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OutboundFrameLogger.java @@ -43,26 +43,26 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter { @Override public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, - ByteBuf data, int padding, boolean endStream, boolean endSegment) { - logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment); - return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment); + ByteBuf data, int padding, boolean endStream) { + logger.logData(OUTBOUND, streamId, data, padding, endStream); + return writer.writeData(ctx, promise, streamId, data, padding, endStream); } @Override public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, - int streamId, Http2Headers headers, int padding, boolean endStream, boolean endSegment) { - logger.logHeaders(OUTBOUND, streamId, headers, padding, endStream, endSegment); - return writer.writeHeaders(ctx, promise, streamId, headers, padding, endStream, endSegment); + int streamId, Http2Headers headers, int padding, boolean endStream) { + logger.logHeaders(OUTBOUND, streamId, headers, padding, endStream); + return writer.writeHeaders(ctx, promise, streamId, headers, padding, endStream); } @Override public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, Http2Headers headers, int streamDependency, short weight, - boolean exclusive, int padding, boolean endStream, boolean endSegment) { + boolean exclusive, int padding, boolean endStream) { logger.logHeaders(OUTBOUND, streamId, headers, streamDependency, weight, exclusive, - padding, endStream, endSegment); + padding, endStream); return writer.writeHeaders(ctx, promise, streamId, headers, streamDependency, weight, - exclusive, padding, endStream, endSegment); + exclusive, padding, endStream); } @Override @@ -140,4 +140,24 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter { public long maxHeaderTableSize() { return writer.maxHeaderTableSize(); } + + @Override + public void maxFrameSize(int max) { + writer.maxFrameSize(max); + } + + @Override + public int maxFrameSize() { + return writer.maxFrameSize(); + } + + @Override + public void maxHeaderListSize(int max) { + writer.maxHeaderListSize(max); + } + + @Override + public int maxHeaderListSize() { + return writer.maxHeaderListSize(); + } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java index f819ed749c..03cb0d1ed7 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java @@ -20,6 +20,9 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_ENABLE_PUSH; import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS; +import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_FRAME_SIZE; +import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_HEADER_LIST_SIZE; +import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid; import io.netty.util.collection.IntObjectHashMap; /** @@ -30,6 +33,7 @@ import io.netty.util.collection.IntObjectHashMap; public final class Http2Settings extends IntObjectHashMap { public Http2Settings() { + this(6 /* number of standard settings */); } public Http2Settings(int initialCapacity, float loadFactor) { @@ -40,21 +44,37 @@ public final class Http2Settings extends IntObjectHashMap { super(initialCapacity); } + /** + * Overrides the superclass method to perform verification of standard HTTP/2 settings. + * + * @throws IllegalArgumentException if verification of the setting fails. + */ @Override public Long put(int key, Long value) { verifyStandardSetting(key, value); return super.put(key, value); } + /** + * Gets the {@code SETTINGS_HEADER_TABLE_SIZE} value. If unavailable, returns {@code null}. + */ public Long headerTableSize() { return get(SETTINGS_HEADER_TABLE_SIZE); } + /** + * Sets the {@code SETTINGS_HEADER_TABLE_SIZE} value. + * + * @throws IllegalArgumentException if verification of the setting fails. + */ public Http2Settings headerTableSize(long value) { put(SETTINGS_HEADER_TABLE_SIZE, value); return this; } + /** + * Gets the {@code SETTINGS_ENABLE_PUSH} value. If unavailable, returns {@code null}. + */ public Boolean pushEnabled() { Long value = get(SETTINGS_ENABLE_PUSH); if (value == null) { @@ -63,39 +83,99 @@ public final class Http2Settings extends IntObjectHashMap { return value != 0L; } + /** + * Sets the {@code SETTINGS_ENABLE_PUSH} value. + */ public Http2Settings pushEnabled(boolean enabled) { - put(SETTINGS_ENABLE_PUSH, enabled? 1L : 0L); + put(SETTINGS_ENABLE_PUSH, enabled ? 1L : 0L); return this; } + /** + * Gets the {@code SETTINGS_MAX_CONCURRENT_STREAMS} value. If unavailable, returns {@code null}. + */ public Long maxConcurrentStreams() { return get(SETTINGS_MAX_CONCURRENT_STREAMS); } + /** + * Sets the {@code SETTINGS_MAX_CONCURRENT_STREAMS} value. + * + * @throws IllegalArgumentException if verification of the setting fails. + */ public Http2Settings maxConcurrentStreams(long value) { put(SETTINGS_MAX_CONCURRENT_STREAMS, value); return this; } + /** + * Gets the {@code SETTINGS_INITIAL_WINDOW_SIZE} value. If unavailable, returns {@code null}. + */ public Integer initialWindowSize() { - Long value = get(SETTINGS_INITIAL_WINDOW_SIZE); - if (value == null) { - return null; - } - return value.intValue(); + return getIntValue(SETTINGS_INITIAL_WINDOW_SIZE); } + /** + * Sets the {@code SETTINGS_INITIAL_WINDOW_SIZE} value. + * + * @throws IllegalArgumentException if verification of the setting fails. + */ public Http2Settings initialWindowSize(int value) { put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value); return this; } + /** + * Gets the {@code SETTINGS_MAX_FRAME_SIZE} value. If unavailable, returns {@code null}. + */ + public Integer maxFrameSize() { + return getIntValue(SETTINGS_MAX_FRAME_SIZE); + } + + /** + * Sets the {@code SETTINGS_MAX_FRAME_SIZE} value. + * + * @throws IllegalArgumentException if verification of the setting fails. + */ + public Http2Settings maxFrameSize(int value) { + put(SETTINGS_MAX_FRAME_SIZE, (long) value); + return this; + } + + /** + * Gets the {@code SETTINGS_MAX_HEADER_LIST_SIZE} value. If unavailable, returns {@code null}. + */ + public Integer maxHeaderListSize() { + return getIntValue(SETTINGS_MAX_HEADER_LIST_SIZE); + } + + /** + * Sets the {@code SETTINGS_MAX_HEADER_LIST_SIZE} value. + * + * @throws IllegalArgumentException if verification of the setting fails. + */ + public Http2Settings maxHeaderListSize(int value) { + put(SETTINGS_MAX_HEADER_LIST_SIZE, (long) value); + return this; + } + + /** + * Clears and then copies the given settings into this object. + */ public Http2Settings copyFrom(Http2Settings settings) { clear(); putAll(settings); return this; } + Integer getIntValue(int key) { + Long value = get(key); + if (value == null) { + return null; + } + return value.intValue(); + } + private void verifyStandardSetting(int key, Long value) { if (value == null) { throw new NullPointerException("value"); @@ -103,7 +183,8 @@ public final class Http2Settings extends IntObjectHashMap { switch (key) { case SETTINGS_HEADER_TABLE_SIZE: if (value < 0L || value > MAX_UNSIGNED_INT) { - throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: " + value); + throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: " + + value); } break; case SETTINGS_ENABLE_PUSH: @@ -113,12 +194,26 @@ public final class Http2Settings extends IntObjectHashMap { break; case SETTINGS_MAX_CONCURRENT_STREAMS: if (value < 0L || value > MAX_UNSIGNED_INT) { - throw new IllegalArgumentException("Setting MAX_CONCURRENT_STREAMS is invalid: " + value); + throw new IllegalArgumentException( + "Setting MAX_CONCURRENT_STREAMS is invalid: " + value); } break; case SETTINGS_INITIAL_WINDOW_SIZE: if (value < 0L || value > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Setting INITIAL_WINDOW_SIZE is invalid: " + value); + throw new IllegalArgumentException("Setting INITIAL_WINDOW_SIZE is invalid: " + + value); + } + break; + case SETTINGS_MAX_FRAME_SIZE: + if (!isMaxFrameSizeValid(value.intValue())) { + throw new IllegalArgumentException("Setting MAX_FRAME_SIZE is invalid: " + + value); + } + break; + case SETTINGS_MAX_HEADER_LIST_SIZE: + if (value < 0) { + throw new IllegalArgumentException("Setting MAX_HEADER_LIST_SIZE is invalid: " + + value); } break; } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapter.java index b7689f9984..a119c774a8 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapter.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapter.java @@ -31,15 +31,12 @@ import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; -import io.netty.util.collection.IntObjectMap; import io.netty.util.collection.IntObjectHashMap; +import io.netty.util.collection.IntObjectMap; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -62,16 +59,16 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements HEADERS_TO_EXCLUDE = new HashSet(); HEADER_NAME_TRANSLATIONS_REQUEST = new HashMap(); HEADER_NAME_TRANSLATIONS_RESPONSE = new HashMap(); - for (Http2Headers.HttpName http2HeaderName : Http2Headers.HttpName.values()) { + for (Http2Headers.PseudoHeaderName http2HeaderName : Http2Headers.PseudoHeaderName.values()) { HEADERS_TO_EXCLUDE.add(http2HeaderName.value()); } - HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.HttpName.AUTHORITY.value(), + HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.AUTHORITY.value(), Http2HttpHeaders.Names.AUTHORITY.toString()); - HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.HttpName.SCHEME.value(), + HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.SCHEME.value(), Http2HttpHeaders.Names.SCHEME.toString()); HEADER_NAME_TRANSLATIONS_REQUEST.putAll(HEADER_NAME_TRANSLATIONS_RESPONSE); - HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.HttpName.PATH.value(), + HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.PATH.value(), Http2HttpHeaders.Names.PATH.toString()); } @@ -155,8 +152,8 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements } @Override - public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream, - boolean endOfSegment) throws Http2Exception { + public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, + boolean endOfStream) throws Http2Exception { // Padding is already stripped out of data by super class Http2HttpMessageAccumulator msgAccumulator = getMessage(streamId); if (msgAccumulator == null) { @@ -241,7 +238,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, - boolean endOfStream, boolean endSegment) throws Http2Exception { + boolean endOfStream) throws Http2Exception { Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(streamId, headers, true); processHeadersEnd(ctx, streamId, msgAccumulator, endOfStream); } @@ -270,8 +267,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, - short weight, boolean exclusive, int padding, boolean endOfStream, boolean endSegment) - throws Http2Exception { + short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(streamId, headers, true); try { setDependencyHeaders(msgAccumulator, streamDependency, weight, exclusive); @@ -332,7 +328,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int padding) throws Http2Exception { // Do not allow adding of headers to existing Http2HttpMessageAccumulator - // according to spec (http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.6) there must + // according to spec (http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.6) there must // be a CONTINUATION frame for more headers Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(promisedStreamId, headers, false); if (msgAccumulator == null) { @@ -574,7 +570,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements throw new IllegalStateException("Headers object is null"); } - // http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.2.1 + // http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.2.3 // All headers that start with ':' are only valid in HTTP/2 context Iterator> itr = http2Headers.iterator(); while (itr.hasNext()) { diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java index 59d9f20090..187a74b631 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java @@ -67,33 +67,33 @@ public class DefaultHttp2FrameIOTest { @Test public void emptyDataShouldRoundtrip() throws Exception { ByteBuf data = Unpooled.EMPTY_BUFFER; - writer.writeData(ctx, promise, 1000, data, 0, false, false); + writer.writeData(ctx, promise, 1000, data, 0, false); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); - verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false)); + verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false)); frame.release(); } @Test public void dataShouldRoundtrip() throws Exception { ByteBuf data = dummyData(); - writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false); + writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); - verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false)); + verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false)); frame.release(); } @Test public void dataWithPaddingShouldRoundtrip() throws Exception { ByteBuf data = dummyData(); - writer.writeData(ctx, promise, 1, data.retain().duplicate(), 0xFF, true, true); + writer.writeData(ctx, promise, 1, data.retain().duplicate(), 0xFF, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); - verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(0xFF), eq(true), eq(true)); + verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(0xFF), eq(true)); frame.release(); } @@ -197,84 +197,84 @@ public class DefaultHttp2FrameIOTest { @Test public void emptyHeadersShouldRoundtrip() throws Exception { Http2Headers headers = Http2Headers.EMPTY_HEADERS; - writer.writeHeaders(ctx, promise, 1, headers, 0, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 0, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); - verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0), eq(true), eq(true)); + verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0), eq(true)); frame.release(); } @Test public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception { Http2Headers headers = Http2Headers.EMPTY_HEADERS; - writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); - verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true)); + verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true)); frame.release(); } @Test public void headersWithoutPriorityShouldRoundtrip() throws Exception { Http2Headers headers = dummyHeaders(); - writer.writeHeaders(ctx, promise, 1, headers, 0, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 0, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); - verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0), eq(true), eq(true)); + verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0), eq(true)); frame.release(); } @Test public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception { Http2Headers headers = dummyHeaders(); - writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); - verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true)); + verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true)); frame.release(); } @Test public void headersWithPriorityShouldRoundtrip() throws Exception { Http2Headers headers = dummyHeaders(); - writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), - eq(true), eq(true)); + eq(true)); frame.release(); } @Test public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception { Http2Headers headers = dummyHeaders(); - writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF), - eq(true), eq(true)); + eq(true)); frame.release(); } @Test public void continuedHeadersShouldRoundtrip() throws Exception { Http2Headers headers = largeHeaders(); - writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), - eq(true), eq(true)); + eq(true)); frame.release(); } @Test public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception { Http2Headers headers = largeHeaders(); - writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true); + writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF), - eq(true), eq(true)); + eq(true)); frame.release(); } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java new file mode 100644 index 0000000000..61dc8f76c8 --- /dev/null +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package io.netty.handler.codec.http2; + +import static io.netty.util.CharsetUtil.UTF_8; +import static org.junit.Assert.assertEquals; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.io.ByteArrayOutputStream; + +import org.junit.Before; +import org.junit.Test; + +import com.twitter.hpack.Encoder; + + +/** + * Tests for {@link DefaultHttp2HeadersDecoder}. + */ +public class DefaultHttp2HeadersDecoderTest { + + private DefaultHttp2HeadersDecoder decoder; + + @Before + public void setup() { + decoder = new DefaultHttp2HeadersDecoder(); + } + + @Test + public void decodeShouldSucceed() throws Exception { + ByteBuf buf = encode(":method", "GET", "akey", "avalue"); + Http2Headers headers = decoder.decodeHeaders(buf); + assertEquals(2, headers.size()); + assertEquals("GET", headers.method()); + assertEquals("avalue", headers.get("akey")); + } + + @Test(expected = Http2Exception.class) + public void decodeWithInvalidPseudoHeaderShouldFail() throws Exception { + ByteBuf buf = encode(":invalid", "GET", "akey", "avalue"); + decoder.decodeHeaders(buf); + } + + private ByteBuf encode(String... entries) throws Exception { + Encoder encoder = new Encoder(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + for (int ix = 0; ix < entries.length;) { + String key = entries[ix++]; + String value = entries[ix++]; + encoder.encodeHeader(stream, key.getBytes(UTF_8), value.getBytes(UTF_8), false); + } + return Unpooled.wrappedBuffer(stream.toByteArray()); + } +} diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java new file mode 100644 index 0000000000..4dc6a83550 --- /dev/null +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package io.netty.handler.codec.http2; + +import static org.junit.Assert.assertTrue; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import org.junit.Before; +import org.junit.Test; + + +/** + * Tests for {@link DefaultHttp2HeadersEncoder}. + */ +public class DefaultHttp2HeadersEncoderTest { + + private DefaultHttp2HeadersEncoder encoder; + + @Before + public void setup() { + encoder = new DefaultHttp2HeadersEncoder(); + } + + @Test + public void encodeShouldSucceed() throws Http2Exception { + DefaultHttp2Headers headers = + DefaultHttp2Headers.newBuilder().method("GET").add("a", "1").add("a", "2").build(); + ByteBuf buf = Unpooled.buffer(); + encoder.encodeHeaders(headers, buf); + assertTrue(buf.writerIndex() > 0); + } + + @Test(expected = Http2Exception.class) + public void headersExceedMaxSetSizeShouldFail() throws Http2Exception { + DefaultHttp2Headers headers = + DefaultHttp2Headers.newBuilder().method("GET").add("a", "1").add("a", "2").build(); + + encoder.maxHeaderListSize(2); + encoder.encodeHeaders(headers, Unpooled.buffer()); + } +} diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java index b767522e67..f803295d8c 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java @@ -17,6 +17,7 @@ package io.netty.handler.codec.http2; import org.junit.Test; +import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -39,11 +40,33 @@ public class DefaultHttp2HeadersTest { .add("a", "3").build(); List aValues = headers.getAll("a"); assertEquals(3, aValues.size()); + assertEquals(3, headers.size()); assertEquals("1", aValues.get(0)); assertEquals("2", aValues.get(1)); assertEquals("3", aValues.get(2)); } + @Test + public void setHeaderShouldReplacePrevious() { + DefaultHttp2Headers headers = + DefaultHttp2Headers.newBuilder().add("a", "1").add("a", "2") + .add("a", "3").set("a", "4").build(); + assertEquals(1, headers.size()); + assertEquals("4", headers.get("a")); + } + + @Test + public void setHeadersShouldReplacePrevious() { + DefaultHttp2Headers headers = + DefaultHttp2Headers.newBuilder().add("a", "1").add("a", "2") + .add("a", "3").set("a", Arrays.asList("4", "5")).build(); + assertEquals(2, headers.size()); + List list = headers.getAll("a"); + assertEquals(2, list.size()); + assertEquals("4", list.get(0)); + assertEquals("5", list.get(1)); + } + @Test(expected = NoSuchElementException.class) public void iterateEmptyHeadersShouldThrow() { Iterator> iterator = @@ -77,4 +100,14 @@ public class DefaultHttp2HeadersTest { // Make sure we removed them all. assertTrue(headers.isEmpty()); } + + @Test(expected = IllegalArgumentException.class) + public void addInvalidPseudoHeaderShouldFail() { + DefaultHttp2Headers.newBuilder().add(":a", "1"); + } + + @Test(expected = IllegalArgumentException.class) + public void setInvalidPseudoHeaderShouldFail() { + DefaultHttp2Headers.newBuilder().set(":a", "1"); + } } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowControllerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowControllerTest.java index ccdb2e879d..d2bffd0684 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowControllerTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2InboundFlowControllerTest.java @@ -118,7 +118,7 @@ public class DefaultHttp2InboundFlowControllerTest { private void applyFlowControl(int dataSize, boolean endOfStream) throws Http2Exception { ByteBuf buf = dummyData(dataSize); - controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false, frameWriter); + controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, frameWriter); buf.release(); } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowControllerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowControllerTest.java index e96c372ee7..5cf1fcc01a 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowControllerTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2OutboundFlowControllerTest.java @@ -18,6 +18,8 @@ package io.netty.handler.codec.http2; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http2.Http2OutboundFlowController.FrameWriter; +import io.netty.util.CharsetUtil; + import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -25,6 +27,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static io.netty.handler.codec.http2.Http2CodecUtil.*; +import static io.netty.util.CharsetUtil.UTF_8; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -60,17 +63,32 @@ public class DefaultHttp2OutboundFlowControllerTest { Http2Stream streamD = connection.local().createStream(STREAM_D, false); streamC.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false); streamD.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false); + + when(frameWriter.maxFrameSize()).thenReturn(Integer.MAX_VALUE); } @Test public void frameShouldBeSentImmediately() throws Http2Exception { ByteBuf data = dummyData(10); - send(STREAM_A, data); + send(STREAM_A, data.slice()); verifyWrite(STREAM_A, data); assertEquals(1, data.refCnt()); data.release(); } + @Test + public void frameShouldSplitForMaxFrameSize() throws Http2Exception { + when(frameWriter.maxFrameSize()).thenReturn(5); + ByteBuf data = dummyData(10); + ByteBuf slice1 = data.slice(data.readerIndex(), 5); + ByteBuf slice2 = data.slice(5, 5); + send(STREAM_A, data.slice()); + verifyWrite(STREAM_A, slice1); + verifyWrite(STREAM_A, slice2); + assertEquals(2, data.refCnt()); + data.release(2); + } + @Test public void stalledStreamShouldQueueFrame() throws Http2Exception { controller.initialOutboundWindowSize(0); @@ -105,7 +123,7 @@ public class DefaultHttp2OutboundFlowControllerTest { controller.initialOutboundWindowSize(0); ByteBuf data = dummyData(10); - send(STREAM_A, data); + send(STREAM_A, data.slice()); verifyNoWrite(STREAM_A); // Verify that the entire frame was sent. @@ -145,7 +163,7 @@ public class DefaultHttp2OutboundFlowControllerTest { .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE); ByteBuf data = dummyData(10); - send(STREAM_A, data); + send(STREAM_A, data.slice()); verifyNoWrite(STREAM_A); // Verify that the entire frame was sent. @@ -186,7 +204,7 @@ public class DefaultHttp2OutboundFlowControllerTest { controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE); ByteBuf data = dummyData(10); - send(STREAM_A, data); + send(STREAM_A, data.slice()); verifyNoWrite(STREAM_A); // Verify that the entire frame was sent. @@ -542,21 +560,20 @@ public class DefaultHttp2OutboundFlowControllerTest { } private void send(int streamId, ByteBuf data) throws Http2Exception { - controller.sendFlowControlled(streamId, data, 0, false, false, frameWriter); + controller.sendFlowControlled(streamId, data, 0, false, frameWriter); } private void verifyWrite(int streamId, ByteBuf data) { - verify(frameWriter).writeFrame(eq(streamId), eq(data), eq(0), eq(false), eq(false)); + verify(frameWriter).writeFrame(eq(streamId), eq(data), eq(0), eq(false)); } private void verifyNoWrite(int streamId) { verify(frameWriter, never()).writeFrame(eq(streamId), any(ByteBuf.class), anyInt(), - anyBoolean(), anyBoolean()); + anyBoolean()); } private void captureWrite(int streamId, ArgumentCaptor captor, boolean endStream) { - verify(frameWriter).writeFrame(eq(streamId), captor.capture(), eq(0), eq(endStream), - eq(false)); + verify(frameWriter).writeFrame(eq(streamId), captor.capture(), eq(0), eq(endStream)); } private void setPriority(int stream, int parent, int weight, boolean exclusive) @@ -565,8 +582,11 @@ public class DefaultHttp2OutboundFlowControllerTest { } private static ByteBuf dummyData(int size) { + String repeatedData = "0123456789"; ByteBuf buffer = Unpooled.buffer(size); - buffer.writerIndex(size); + for (int index = 0; index < size; ++index) { + buffer.writeByte(repeatedData.charAt(index % repeatedData.length())); + } return buffer; } } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java index ea8c7d353e..95d26e6d4e 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java @@ -18,6 +18,7 @@ package io.netty.handler.codec.http2; import static io.netty.buffer.Unpooled.EMPTY_BUFFER; import static io.netty.buffer.Unpooled.copiedBuffer; import static io.netty.buffer.Unpooled.wrappedBuffer; +import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT; import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf; import static io.netty.handler.codec.http2.Http2CodecUtil.emptyPingBuf; @@ -146,10 +147,16 @@ public class DelegatingHttp2ConnectionHandlerTest { settings.pushEnabled(true); settings.maxConcurrentStreams(100); settings.headerTableSize(200); + settings.maxFrameSize(DEFAULT_MAX_FRAME_SIZE); + settings.maxHeaderListSize(Integer.MAX_VALUE); when(inboundFlow.initialInboundWindowSize()).thenReturn(10); when(local.allowPushTo()).thenReturn(true); when(remote.maxStreams()).thenReturn(100); when(reader.maxHeaderTableSize()).thenReturn(200L); + when(reader.maxFrameSize()).thenReturn(DEFAULT_MAX_FRAME_SIZE); + when(writer.maxFrameSize()).thenReturn(DEFAULT_MAX_FRAME_SIZE); + when(reader.maxHeaderListSize()).thenReturn(Integer.MAX_VALUE); + when(writer.maxHeaderListSize()).thenReturn(Integer.MAX_VALUE); handler.handlerAdded(ctx); verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings)); @@ -246,72 +253,71 @@ public class DelegatingHttp2ConnectionHandlerTest { @Test public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception { when(remote.isGoAwayReceived()).thenReturn(true); - decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true); + decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true); verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10), - eq(true), eq(true), any(Http2InboundFlowController.FrameWriter.class)); + eq(true), any(Http2InboundFlowController.FrameWriter.class)); // Verify that the event was absorbed and not propagated to the oberver. verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), - anyBoolean(), anyBoolean()); + anyBoolean()); } @Test public void dataReadWithEndOfStreamShouldCloseRemoteSide() throws Exception { - decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, false); + decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true); verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10), - eq(true), eq(false), any(Http2InboundFlowController.FrameWriter.class)); + eq(true), any(Http2InboundFlowController.FrameWriter.class)); verify(stream).closeRemoteSide(); - verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true), - eq(false)); + verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true)); } @Test public void headersReadAfterGoAwayShouldBeIgnored() throws Exception { when(remote.isGoAwayReceived()).thenReturn(true); - decode().onHeadersRead(ctx, STREAM_ID, EMPTY_HEADERS, 0, false, false); + decode().onHeadersRead(ctx, STREAM_ID, EMPTY_HEADERS, 0, false); verify(remote, never()).createStream(eq(STREAM_ID), eq(false)); // Verify that the event was absorbed and not propagated to the oberver. verify(observer, never()).onHeadersRead(eq(ctx), anyInt(), any(Http2Headers.class), - anyInt(), anyBoolean(), anyBoolean()); + anyInt(), anyBoolean()); verify(remote, never()).createStream(anyInt(), anyBoolean()); } @Test public void headersReadForUnknownStreamShouldCreateStream() throws Exception { when(remote.createStream(eq(5), eq(false))).thenReturn(stream); - decode().onHeadersRead(ctx, 5, EMPTY_HEADERS, 0, false, false); + decode().onHeadersRead(ctx, 5, EMPTY_HEADERS, 0, false); verify(remote).createStream(eq(5), eq(false)); verify(observer).onHeadersRead(eq(ctx), eq(5), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false)); } @Test public void headersReadForUnknownStreamShouldCreateHalfClosedStream() throws Exception { when(remote.createStream(eq(5), eq(true))).thenReturn(stream); - decode().onHeadersRead(ctx, 5, EMPTY_HEADERS, 0, true, false); + decode().onHeadersRead(ctx, 5, EMPTY_HEADERS, 0, true); verify(remote).createStream(eq(5), eq(true)); verify(observer).onHeadersRead(eq(ctx), eq(5), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true)); } @Test public void headersReadForPromisedStreamShouldHalfOpenStream() throws Exception { when(stream.state()).thenReturn(RESERVED_REMOTE); - decode().onHeadersRead(ctx, STREAM_ID, EMPTY_HEADERS, 0, false, false); + decode().onHeadersRead(ctx, STREAM_ID, EMPTY_HEADERS, 0, false); verify(stream).openForPush(); verify(observer).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false)); } @Test public void headersReadForPromisedStreamShouldCloseStream() throws Exception { when(stream.state()).thenReturn(RESERVED_REMOTE); - decode().onHeadersRead(ctx, STREAM_ID, EMPTY_HEADERS, 0, true, false); + decode().onHeadersRead(ctx, STREAM_ID, EMPTY_HEADERS, 0, true); verify(stream).openForPush(); verify(stream).close(); verify(observer).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true)); } @Test @@ -444,14 +450,14 @@ public class DelegatingHttp2ConnectionHandlerTest { @Test public void dataWriteAfterGoAwayShouldFail() throws Exception { when(connection.isGoAway()).thenReturn(true); - ChannelFuture future = handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false); + ChannelFuture future = handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false); assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception); } @Test public void dataWriteShouldSucceed() throws Exception { - handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false); - verify(outboundFlow).sendFlowControlled(eq(STREAM_ID), eq(dummyData()), eq(0), eq(false), + handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false); + verify(outboundFlow).sendFlowControlled(eq(STREAM_ID), eq(dummyData()), eq(0), eq(false), any(Http2OutboundFlowController.FrameWriter.class)); } @@ -459,49 +465,49 @@ public class DelegatingHttp2ConnectionHandlerTest { public void headersWriteAfterGoAwayShouldFail() throws Exception { when(connection.isGoAway()).thenReturn(true); ChannelFuture future = handler.writeHeaders( - ctx, promise, 5, EMPTY_HEADERS, 0, (short) 255, false, 0, false, false); + ctx, promise, 5, EMPTY_HEADERS, 0, (short) 255, false, 0, false); verify(local, never()).createStream(anyInt(), anyBoolean()); verify(writer, never()).writeHeaders(eq(ctx), eq(promise), anyInt(), - any(Http2Headers.class), anyInt(), anyBoolean(), anyBoolean()); + any(Http2Headers.class), anyInt(), anyBoolean()); assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception); } @Test public void headersWriteForUnknownStreamShouldCreateStream() throws Exception { when(local.createStream(eq(5), eq(false))).thenReturn(stream); - handler.writeHeaders(ctx, promise, 5, EMPTY_HEADERS, 0, false, false); + handler.writeHeaders(ctx, promise, 5, EMPTY_HEADERS, 0, false); verify(local).createStream(eq(5), eq(false)); verify(writer).writeHeaders(eq(ctx), eq(promise), eq(5), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false)); } @Test public void headersWriteShouldCreateHalfClosedStream() throws Exception { when(local.createStream(eq(5), eq(true))).thenReturn(stream); - handler.writeHeaders(ctx, promise, 5, EMPTY_HEADERS, 0, true, false); + handler.writeHeaders(ctx, promise, 5, EMPTY_HEADERS, 0, true); verify(local).createStream(eq(5), eq(true)); verify(writer).writeHeaders(eq(ctx), eq(promise), eq(5), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true)); } @Test public void headersWriteShouldOpenStreamForPush() throws Exception { when(stream.state()).thenReturn(RESERVED_LOCAL); - handler.writeHeaders(ctx, promise, STREAM_ID, EMPTY_HEADERS, 0, false, false); + handler.writeHeaders(ctx, promise, STREAM_ID, EMPTY_HEADERS, 0, false); verify(stream).openForPush(); verify(stream, never()).closeLocalSide(); verify(writer).writeHeaders(eq(ctx), eq(promise), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false)); } @Test public void headersWriteShouldClosePushStream() throws Exception { when(stream.state()).thenReturn(RESERVED_LOCAL).thenReturn(HALF_CLOSED_LOCAL); - handler.writeHeaders(ctx, promise, STREAM_ID, EMPTY_HEADERS, 0, true, false); + handler.writeHeaders(ctx, promise, STREAM_ID, EMPTY_HEADERS, 0, true); verify(stream).openForPush(); verify(stream).closeLocalSide(); verify(writer).writeHeaders(eq(ctx), eq(promise), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(false)); + eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true)); } @Test diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandlerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandlerTest.java index 75202824fb..dcdb8f56ca 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandlerTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2HttpConnectionHandlerTest.java @@ -14,6 +14,20 @@ */ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static io.netty.handler.codec.http2.Http2CodecUtil.ignoreSettingsHandler; +import static io.netty.util.CharsetUtil.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyShort; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; @@ -27,45 +41,21 @@ import io.netty.channel.ChannelPromise; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.http.DefaultLastHttpContent; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaderUtil; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.DefaultHttpRequest; -import io.netty.handler.codec.http.DefaultHttpHeaders; -import io.netty.handler.codec.http.HttpMessage; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; -import static io.netty.handler.codec.http.HttpMethod.GET; -import static io.netty.handler.codec.http.HttpMethod.POST; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import io.netty.handler.codec.http.DefaultFullHttpRequest; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable; import io.netty.util.NetUtil; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; + import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.net.InetSocketAddress; -import java.util.List; -import java.util.concurrent.CountDownLatch; - -import static io.netty.handler.codec.http2.Http2TestUtil.*; -import static io.netty.util.CharsetUtil.*; -import static java.util.concurrent.TimeUnit.*; -import static org.junit.Assert.*; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - /** * Testing the {@link DelegatingHttp2HttpConnectionHandler} for {@link FullHttpRequest} objects into HTTP/2 frames */ @@ -77,22 +67,18 @@ public class DelegatingHttp2HttpConnectionHandlerTest { @Mock private Http2FrameObserver serverObserver; - private Http2FrameWriter frameWriter; private ServerBootstrap sb; private Bootstrap cb; private Channel serverChannel; private Channel clientChannel; private CountDownLatch requestLatch; - private long maxContentLength; private static final int CONNECTION_SETUP_READ_COUNT = 2; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); - maxContentLength = 1 << 16; requestLatch = new CountDownLatch(CONNECTION_SETUP_READ_COUNT + 1); - frameWriter = new DefaultHttp2FrameWriter(); sb = new ServerBootstrap(); cb = new Bootstrap(); @@ -104,6 +90,7 @@ public class DelegatingHttp2HttpConnectionHandlerTest { protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new DelegatingHttp2ConnectionHandler(true, new FrameCountDown())); + p.addLast(ignoreSettingsHandler()); } }); @@ -114,6 +101,7 @@ public class DelegatingHttp2HttpConnectionHandlerTest { protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new DelegatingHttp2HttpConnectionHandler(false, clientObserver)); + p.addLast(ignoreSettingsHandler()); } }); @@ -155,9 +143,9 @@ public class DelegatingHttp2HttpConnectionHandlerTest { assertTrue(writeFuture.isSuccess()); awaitRequests(); verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(5), eq(http2Headers), eq(0), - anyShort(), anyBoolean(), eq(0), eq(true), eq(true)); + anyShort(), anyBoolean(), eq(0), eq(true)); verify(serverObserver, never()).onDataRead(any(ChannelHandlerContext.class), - anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(), anyBoolean()); + anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()); } @Test @@ -174,8 +162,6 @@ public class DelegatingHttp2HttpConnectionHandlerTest { final Http2Headers http2Headers = new DefaultHttp2Headers.Builder() .method("POST").path("/example").authority("www.example.org:5555").scheme("http") .add("foo", "goo").add("foo", "goo2").add("foo2", "goo2").build(); - final HttpContent expectedContent = new DefaultLastHttpContent(Unpooled.copiedBuffer(text.getBytes()), - true); ChannelPromise writePromise = newPromise(); ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise); @@ -185,9 +171,9 @@ public class DelegatingHttp2HttpConnectionHandlerTest { assertTrue(writeFuture.isSuccess()); awaitRequests(); verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2Headers), eq(0), - anyShort(), anyBoolean(), eq(0), eq(false), eq(false)); + anyShort(), anyBoolean(), eq(0), eq(false)); verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), - eq(3), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true)); + eq(3), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true)); } private void awaitRequests() throws Exception { @@ -210,26 +196,25 @@ public class DelegatingHttp2HttpConnectionHandlerTest { @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) + boolean endOfStream) throws Http2Exception { - serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream, - endOfSegment); + serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int padding, boolean endStream, boolean endSegment) throws Http2Exception { - serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); + int padding, boolean endStream) throws Http2Exception { + serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, - boolean endStream, boolean endSegment) throws Http2Exception { + boolean endStream) throws Http2Exception { serverObserver.onHeadersRead(ctx, streamId, headers, streamDependency, weight, - exclusive, padding, endStream, endSegment); + exclusive, padding, endStream); requestLatch.countDown(); } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java index 4f6ade8d95..308f7ca752 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java @@ -76,6 +76,7 @@ public class Http2ConnectionRoundtripTest { protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new DelegatingHttp2ConnectionHandler(true, new FrameCountDown())); + p.addLast(Http2CodecUtil.ignoreSettingsHandler()); } }); @@ -86,6 +87,7 @@ public class Http2ConnectionRoundtripTest { protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new DelegatingHttp2ConnectionHandler(false, clientObserver)); + p.addLast(Http2CodecUtil.ignoreSettingsHandler()); } }); @@ -117,23 +119,22 @@ public class Http2ConnectionRoundtripTest { public void run() { for (int i = 0, nextStream = 3; i < NUM_STREAMS; ++i, nextStream += 2) { http2Client.writeHeaders( - ctx(), newPromise(), nextStream, headers, 0, (short) 16, false, 0, false, false); + ctx(), newPromise(), nextStream, headers, 0, (short) 16, false, 0, false); http2Client.writePing(ctx(), newPromise(), Unpooled.copiedBuffer(pingMsg.getBytes())); http2Client.writeData( ctx(), newPromise(), nextStream, - Unpooled.copiedBuffer(text.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, true); } } }); // Wait for all frames to be received. awaitRequests(); verify(serverObserver, times(NUM_STREAMS)).onHeadersRead(any(ChannelHandlerContext.class), - anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false), - eq(false)); + anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false)); verify(serverObserver, times(NUM_STREAMS)).onPingRead(any(ChannelHandlerContext.class), eq(Unpooled.copiedBuffer(pingMsg.getBytes()))); verify(serverObserver, times(NUM_STREAMS)).onDataRead(any(ChannelHandlerContext.class), - anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true)); + anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true)); } private void awaitRequests() throws Exception { @@ -156,26 +157,25 @@ public class Http2ConnectionRoundtripTest { @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) + boolean endOfStream) throws Http2Exception { - serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream, - endOfSegment); + serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, - int padding, boolean endStream, boolean endSegment) throws Http2Exception { - serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); + int padding, boolean endStream) throws Http2Exception { + serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, - boolean endStream, boolean endSegment) throws Http2Exception { + boolean endStream) throws Http2Exception { serverObserver.onHeadersRead(ctx, streamId, headers, streamDependency, weight, - exclusive, padding, endStream, endSegment); + exclusive, padding, endStream); requestLatch.countDown(); } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java index 20b512b469..598e8dfef1 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java @@ -30,6 +30,7 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.util.NetUtil; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -81,6 +82,7 @@ public class Http2FrameRoundtripTest { protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast("reader", new FrameAdapter(serverObserver)); + p.addLast(Http2CodecUtil.ignoreSettingsHandler()); } }); @@ -91,6 +93,7 @@ public class Http2FrameRoundtripTest { protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast("reader", new FrameAdapter(null)); + p.addLast(Http2CodecUtil.ignoreSettingsHandler()); } }); @@ -116,12 +119,12 @@ public class Http2FrameRoundtripTest { @Override public void run() { frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF, - Unpooled.copiedBuffer(text.getBytes()), 100, true, false); + Unpooled.copiedBuffer(text.getBytes()), 100, true); } }); awaitRequests(); verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), - dataCaptor.capture(), eq(100), eq(true), eq(false)); + dataCaptor.capture(), eq(100), eq(true)); } @Test @@ -132,12 +135,12 @@ public class Http2FrameRoundtripTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 0, true, false); + frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 0, true); } }); awaitRequests(); verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), - eq(headers), eq(0), eq(true), eq(false)); + eq(headers), eq(0), eq(true)); } @Test @@ -149,12 +152,12 @@ public class Http2FrameRoundtripTest { @Override public void run() { frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 4, (short) 255, - true, 0, true, false); + true, 0, true); } }); awaitRequests(); verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), - eq(headers), eq(4), eq((short) 255), eq(true), eq(0), eq(true), eq(false)); + eq(headers), eq(4), eq((short) 255), eq(true), eq(0), eq(true)); } @Test @@ -271,9 +274,9 @@ public class Http2FrameRoundtripTest { public void run() { for (int i = 1; i < numStreams + 1; ++i) { frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false, - 0, false, false); + 0, false); frameWriter.writeData(ctx(), newPromise(), i, - Unpooled.copiedBuffer(text.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, true); } } }); @@ -309,28 +312,27 @@ public class Http2FrameRoundtripTest { @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, - int padding, boolean endOfStream, boolean endOfSegment) + int padding, boolean endOfStream) throws Http2Exception { - observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream, - endOfSegment); + observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, - Http2Headers headers, int padding, boolean endStream, boolean endSegment) + Http2Headers headers, int padding, boolean endStream) throws Http2Exception { - observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); + observer.onHeadersRead(ctx, streamId, headers, padding, endStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, - boolean exclusive, int padding, boolean endStream, boolean endSegment) + boolean exclusive, int padding, boolean endStream) throws Http2Exception { observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, - exclusive, padding, endStream, endSegment); + exclusive, padding, endStream); requestLatch.countDown(); } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java index edc022304c..38e8d2ce7f 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java @@ -15,6 +15,7 @@ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_SIZE_UPPER_BOUND; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -41,6 +42,8 @@ public class Http2SettingsTest { assertNull(settings.initialWindowSize()); assertNull(settings.maxConcurrentStreams()); assertNull(settings.pushEnabled()); + assertNull(settings.maxFrameSize()); + assertNull(settings.maxHeaderListSize()); } @Test @@ -49,9 +52,13 @@ public class Http2SettingsTest { settings.maxConcurrentStreams(2); settings.pushEnabled(true); settings.headerTableSize(3); + settings.maxFrameSize(MAX_FRAME_SIZE_UPPER_BOUND); + settings.maxHeaderListSize(4); assertEquals(1, (int) settings.initialWindowSize()); assertEquals(2L, (long) settings.maxConcurrentStreams()); assertTrue(settings.pushEnabled()); assertEquals(3L, (long) settings.headerTableSize()); + assertEquals(MAX_FRAME_SIZE_UPPER_BOUND, (int) settings.maxFrameSize()); + assertEquals(4L, (long) settings.maxHeaderListSize()); } } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java index a521b8d9b8..0772a3e93e 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java @@ -14,6 +14,13 @@ */ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; @@ -24,30 +31,28 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; +import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.DefaultHttpRequest; -import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.DefaultLastHttpContent; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaderUtil; import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpMessage; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable; import io.netty.util.NetUtil; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.CountDownLatch; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -55,17 +60,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.net.InetSocketAddress; -import java.util.List; -import java.util.concurrent.CountDownLatch; - -import static io.netty.handler.codec.http2.Http2TestUtil.*; -import static io.netty.util.CharsetUtil.*; -import static java.util.concurrent.TimeUnit.*; -import static org.junit.Assert.*; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - /** * Testing the {@link InboundHttp2ToHttpAdapter} for HTTP/2 frames into {@link HttpObject}s */ @@ -151,7 +145,7 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, true, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, true); } }); awaitRequests(); @@ -174,9 +168,9 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, true); } }); awaitRequests(); @@ -205,11 +199,11 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, false, false); + Unpooled.copiedBuffer(text.getBytes()), 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text2.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text2.getBytes()), 0, true); } }); awaitRequests(); @@ -238,13 +232,13 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, false, false); + Unpooled.copiedBuffer(text.getBytes()), 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, false, false); + Unpooled.copiedBuffer(text.getBytes()), 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, true); } }); awaitRequests(); @@ -278,10 +272,10 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, false, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, true); } }); awaitRequests(); @@ -315,10 +309,10 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, false, false); - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, true); } }); awaitRequests(); @@ -359,12 +353,12 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false); frameWriter.writePushPromise(ctx(), newPromise(), 3, 5, http2Headers2, 0); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, true); frameWriter.writeData(ctx(), newPromise(), 5, - Unpooled.copiedBuffer(text2.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text2.getBytes()), 0, true); } }); awaitRequests(); @@ -405,13 +399,13 @@ public class InboundHttp2ToHttpAdapterTest { runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { - frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); - frameWriter.writeHeaders(ctx(), newPromise(), 5, http2Headers2, 0, false, false); + frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false); + frameWriter.writeHeaders(ctx(), newPromise(), 5, http2Headers2, 0, false); frameWriter.writePriority(ctx(), newPromise(), 5, 3, (short) 256, true); frameWriter.writeData(ctx(), newPromise(), 3, - Unpooled.copiedBuffer(text.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text.getBytes()), 0, true); frameWriter.writeData(ctx(), newPromise(), 5, - Unpooled.copiedBuffer(text2.getBytes()), 0, true, true); + Unpooled.copiedBuffer(text2.getBytes()), 0, true); } }); awaitRequests(); @@ -474,28 +468,27 @@ public class InboundHttp2ToHttpAdapterTest { @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, - int padding, boolean endOfStream, boolean endOfSegment) + int padding, boolean endOfStream) throws Http2Exception { - observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream, - endOfSegment); + observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, - Http2Headers headers, int padding, boolean endStream, boolean endSegment) + Http2Headers headers, int padding, boolean endStream) throws Http2Exception { - observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); + observer.onHeadersRead(ctx, streamId, headers, padding, endStream); requestLatch.countDown(); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, - boolean exclusive, int padding, boolean endStream, boolean endSegment) + boolean exclusive, int padding, boolean endStream) throws Http2Exception { observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, - exclusive, padding, endStream, endSegment); + exclusive, padding, endStream); requestLatch.countDown(); } diff --git a/example/src/main/java/io/netty/example/http2/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/client/Http2Client.java index 9b4759ba6a..b60f58b8ea 100644 --- a/example/src/main/java/io/netty/example/http2/client/Http2Client.java +++ b/example/src/main/java/io/netty/example/http2/client/Http2Client.java @@ -14,10 +14,12 @@ */ package io.netty.example.http2.client; +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; @@ -34,9 +36,6 @@ import java.net.URI; import java.util.Arrays; import java.util.concurrent.TimeUnit; -import static io.netty.handler.codec.http.HttpMethod.*; -import static io.netty.handler.codec.http.HttpVersion.*; - /** * An HTTP2 client that allows you to send HTTP2 frames to a server. Inbound and outbound frames are * logged. When run from the command-line, sends a single HEADERS frame to the server and gets back diff --git a/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java b/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java index 6b55923791..7b202a0b8b 100644 --- a/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java +++ b/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java @@ -22,15 +22,11 @@ import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http2.Http2HttpHeaders; import io.netty.util.CharsetUtil; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; import java.util.Iterator; import java.util.Map.Entry; -import java.util.concurrent.TimeUnit; import java.util.SortedMap; import java.util.TreeMap; +import java.util.concurrent.TimeUnit; /** * Process {@link FullHttpResponse} translated from HTTP/2 frames diff --git a/example/src/main/java/io/netty/example/http2/server/HelloWorldHttp2Handler.java b/example/src/main/java/io/netty/example/http2/server/HelloWorldHttp2Handler.java index 75e1ab8280..3d91ba9cf1 100644 --- a/example/src/main/java/io/netty/example/http2/server/HelloWorldHttp2Handler.java +++ b/example/src/main/java/io/netty/example/http2/server/HelloWorldHttp2Handler.java @@ -69,7 +69,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler { Http2Headers headers = DefaultHttp2Headers.newBuilder().status("200") .set(UPGRADE_RESPONSE_HEADER, "true").build(); - writeHeaders(ctx, ctx.newPromise(), 1, headers, 0, true, true); + writeHeaders(ctx, ctx.newPromise(), 1, headers, 0, true); } super.userEventTriggered(ctx, evt); } @@ -79,7 +79,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler { */ @Override public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, - boolean endOfStream, boolean endOfSegment) throws Http2Exception { + boolean endOfStream) throws Http2Exception { if (endOfStream) { sendResponse(ctx(), streamId, data.retain()); } @@ -91,8 +91,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler { @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, - boolean exclusive, int padding, boolean endStream, boolean endSegment) - throws Http2Exception { + boolean exclusive, int padding, boolean endStream) throws Http2Exception { if (endStream) { sendResponse(ctx(), streamId, RESPONSE_BYTES.duplicate()); } @@ -110,8 +109,8 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler { private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) { // Send a frame for the response status Http2Headers headers = DefaultHttp2Headers.newBuilder().status("200").build(); - writeHeaders(ctx(), ctx().newPromise(), streamId, headers, 0, false, false); + writeHeaders(ctx(), ctx().newPromise(), streamId, headers, 0, false); - writeData(ctx(), ctx().newPromise(), streamId, payload, 0, true, true); + writeData(ctx(), ctx().newPromise(), streamId, payload, 0, true); } }