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.
This commit is contained in:
nmittler 2014-08-15 14:57:02 -07:00
parent 26116541ed
commit 0c817d61b3
43 changed files with 1056 additions and 433 deletions

View File

@ -43,7 +43,7 @@
<dependency>
<groupId>com.twitter</groupId>
<artifactId>hpack</artifactId>
<version>0.8.0</version>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>

View File

@ -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.

View File

@ -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);
}
}
}

View File

@ -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.");
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<String> sensitiveHeaders = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
private int maxHeaderListSize = Integer.MAX_VALUE;
public DefaultHttp2HeadersEncoder() {
this(DEFAULT_HEADER_TABLE_SIZE);
this(DEFAULT_HEADER_TABLE_SIZE, Collections.<String>emptySet());
}
public DefaultHttp2HeadersEncoder(int maxHeaderTableSize) {
public DefaultHttp2HeadersEncoder(int maxHeaderTableSize, Set<String> 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<String, String> 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<String, String> 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);
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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() {
}
}

View File

@ -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,");
}

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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.
*/

View File

@ -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();
}

View File

@ -56,6 +56,11 @@ public abstract class Http2Headers implements Iterable<Entry<String, String>> {
return true;
}
@Override
public int size() {
return 0;
}
@Override
public Set<String> names() {
return Collections.emptySet();
@ -68,43 +73,68 @@ public abstract class Http2Headers implements Iterable<Entry<String, String>> {
};
/**
* 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<Entry<String, String>> {
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

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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");

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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<Long> {
public Http2Settings() {
this(6 /* number of standard settings */);
}
public Http2Settings(int initialCapacity, float loadFactor) {
@ -40,21 +44,37 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
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<Long> {
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<Long> {
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<Long> {
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;
}

View File

@ -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<String>();
HEADER_NAME_TRANSLATIONS_REQUEST = new HashMap<String, String>();
HEADER_NAME_TRANSLATIONS_RESPONSE = new HashMap<String, String>();
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<Entry<String, String>> itr = http2Headers.iterator();
while (itr.hasNext()) {

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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<String> 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<String> 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<Map.Entry<String, String>> 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");
}
}

View File

@ -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();
}

View File

@ -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<ByteBuf> 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;
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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);
}
}