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> <dependency>
<groupId>com.twitter</groupId> <groupId>com.twitter</groupId>
<artifactId>hpack</artifactId> <artifactId>hpack</artifactId>
<version>0.8.0</version> <version>0.9.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>

View File

@ -210,6 +210,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
settings.initialWindowSize(inboundFlow.initialInboundWindowSize()); settings.initialWindowSize(inboundFlow.initialInboundWindowSize());
settings.maxConcurrentStreams(connection.remote().maxStreams()); settings.maxConcurrentStreams(connection.remote().maxStreams());
settings.headerTableSize(frameReader.maxHeaderTableSize()); settings.headerTableSize(frameReader.maxHeaderTableSize());
settings.maxFrameSize(frameReader.maxFrameSize());
settings.maxHeaderListSize(frameReader.maxHeaderListSize());
if (!connection.isServer()) { if (!connection.isServer()) {
// Only set the pushEnabled flag if this is a client endpoint. // Only set the pushEnabled flag if this is a client endpoint.
settings.pushEnabled(connection.local().allowPushTo()); settings.pushEnabled(connection.local().allowPushTo());
@ -222,7 +224,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
*/ */
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, 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 @Override
public final void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, 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 @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
boolean endSegment) throws Http2Exception { throws Http2Exception {
} }
/** /**
@ -338,7 +340,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
protected ChannelFuture writeData(final ChannelHandlerContext ctx, protected ChannelFuture writeData(final ChannelHandlerContext ctx,
final ChannelPromise promise, int streamId, final ByteBuf data, int padding, final ChannelPromise promise, int streamId, final ByteBuf data, int padding,
boolean endStream, boolean endSegment) { boolean endStream) {
try { try {
if (connection.isGoAway()) { if (connection.isGoAway()) {
throw protocolError("Sending data after connection going away."); 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); stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
// Hand control of the frame to the flow controller. // 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)); new FlowControlWriter(ctx, data, promise));
return promise; return promise;
@ -358,14 +360,14 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
} }
protected ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, 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, return writeHeaders(ctx, promise, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false,
padding, endStream, endSegment); padding, endStream);
} }
protected ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, protected ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise,
int streamId, Http2Headers headers, int streamDependency, short weight, int streamId, Http2Headers headers, int streamDependency, short weight,
boolean exclusive, int padding, boolean endStream, boolean endSegment) { boolean exclusive, int padding, boolean endStream) {
try { try {
if (connection.isGoAway()) { if (connection.isGoAway()) {
throw protocolError("Sending headers after connection going away."); 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, 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 the headers are the end of the stream, close it now.
if (endStream) { if (endStream) {
@ -506,6 +508,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
frameReader.readFrame(ctx, in, internalFrameObserver); frameReader.readFrame(ctx, in, internalFrameObserver);
} catch (Http2Exception e) { } catch (Http2Exception e) {
onHttp2Exception(ctx, 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); 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(); Integer initialWindowSize = settings.initialWindowSize();
if (initialWindowSize != null) { if (initialWindowSize != null) {
outboundFlow.initialOutboundWindowSize(initialWindowSize); outboundFlow.initialOutboundWindowSize(initialWindowSize);
@ -760,7 +779,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
@Override @Override
public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment) throws Http2Exception { boolean endOfStream) throws Http2Exception {
verifyPrefaceReceived(); verifyPrefaceReceived();
// Check if we received a data frame for a stream which is half-closed // 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); stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
// Apply flow control. // Apply flow control.
inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, endOfSegment, inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream,
new Http2InboundFlowController.FrameWriter() { new Http2InboundFlowController.FrameWriter() {
@Override @Override
public void writeFrame(int streamId, int windowSizeIncrement) public void writeFrame(int streamId, int windowSizeIncrement)
@ -785,8 +804,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
return; return;
} }
AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream, AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream);
endOfSegment);
if (endOfStream) { if (endOfStream) {
closeRemoteSide(stream, ctx.newSucceededFuture()); closeRemoteSide(stream, ctx.newSucceededFuture());
@ -824,15 +842,15 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, 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, onHeadersRead(ctx, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, padding,
endStream, endSegment); endStream);
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, int streamDependency, short weight, boolean exclusive, int padding,
boolean endStream, boolean endSegment) throws Http2Exception { boolean endStream) throws Http2Exception {
verifyPrefaceReceived(); verifyPrefaceReceived();
Http2Stream stream = connection.stream(streamId); Http2Stream stream = connection.stream(streamId);
@ -865,7 +883,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
} }
AbstractHttp2ConnectionHandler.this.onHeadersRead(ctx, streamId, headers, streamDependency, 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 the headers completes this stream, close it.
if (endStream) { if (endStream) {
@ -948,6 +966,21 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
frameReader.maxHeaderTableSize(headerTableSize); 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(); Integer initialWindowSize = settings.initialWindowSize();
if (initialWindowSize != null) { if (initialWindowSize != null) {
inboundFlow.initialInboundWindowSize(initialWindowSize); inboundFlow.initialInboundWindowSize(initialWindowSize);
@ -1098,8 +1131,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
} }
@Override @Override
public void writeFrame(int streamId, ByteBuf data, int padding, public void writeFrame(int streamId, ByteBuf data, int padding, boolean endStream) {
boolean endStream, boolean endSegment) {
if (promise.isDone()) { if (promise.isDone()) {
// Most likely the write already failed. Just release the // Most likely the write already failed. Just release the
// buffer. // buffer.
@ -1128,8 +1160,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
// Write the frame. // Write the frame.
ChannelFuture future = ChannelFuture future =
frameWriter.writeData(ctx, chunkPromise, streamId, data, padding, endStream, frameWriter.writeData(ctx, chunkPromise, streamId, data, padding, endStream);
endSegment);
// Close the connection on write failures that leave the outbound // Close the connection on write failures that leave the outbound
// flow control window in a corrupt state. // flow control window in a corrupt state.
@ -1160,6 +1191,11 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
failAllPromises(cause); failAllPromises(cause);
} }
@Override
public int maxFrameSize() {
return frameWriter.maxFrameSize();
}
/** /**
* Called when the write for any chunk fails. Fails all promises including * Called when the write for any chunk fails. Fails all promises including
* the one returned to the caller. * the one returned to the caller.

View File

@ -15,12 +15,13 @@
package io.netty.handler.codec.http2; 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_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.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.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.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.Http2CodecUtil.readUnsignedInt;
import static io.netty.handler.codec.http2.Http2Exception.protocolError; import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION; import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
@ -56,6 +57,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
private Http2Flags flags; private Http2Flags flags;
private int payloadLength; private int payloadLength;
private HeadersContinuation headersContinuation; private HeadersContinuation headersContinuation;
private int maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
public DefaultHttp2FrameReader() { public DefaultHttp2FrameReader() {
this(new DefaultHttp2HeadersDecoder()); this(new DefaultHttp2HeadersDecoder());
@ -75,6 +77,29 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
return headersDecoder.maxHeaderTableSize(); 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 @Override
public void close() { public void close() {
if (headersContinuation != null) { if (headersContinuation != null) {
@ -133,7 +158,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
} }
// Read the header and prepare the unmarshaller to read the frame. // 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(); frameType = in.readByte();
flags = new Http2Flags(in.readUnsignedByte()); flags = new Http2Flags(in.readUnsignedByte());
streamId = readUnsignedInt(in); streamId = readUnsignedInt(in);
@ -351,8 +379,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
} }
ByteBuf data = payload.readSlice(dataLength); ByteBuf data = payload.readSlice(dataLength);
observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream(), observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream());
flags.endOfSegment());
payload.skipBytes(payload.readableBytes()); payload.skipBytes(payload.readableBytes());
} }
@ -385,8 +412,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
if (endOfHeaders) { if (endOfHeaders) {
Http2Headers headers = builder().buildHeaders(); Http2Headers headers = builder().buildHeaders();
observer.onHeadersRead(ctx, headersStreamId, headers, streamDependency, observer.onHeadersRead(ctx, headersStreamId, headers, streamDependency,
weight, exclusive, padding, headersFlags.endOfStream(), weight, exclusive, padding, headersFlags.endOfStream());
headersFlags.endOfSegment());
close(); close();
} }
} }
@ -412,7 +438,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
if (endOfHeaders) { if (endOfHeaders) {
Http2Headers headers = builder().buildHeaders(); Http2Headers headers = builder().buildHeaders();
observer.onHeadersRead(ctx, headersStreamId, headers, padding, observer.onHeadersRead(ctx, headersStreamId, headers, padding,
headersFlags.endOfStream(), headersFlags.endOfSegment()); headersFlags.endOfStream());
close(); close();
} }
} }
@ -444,11 +470,19 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
observer.onSettingsAckRead(ctx); observer.onSettingsAckRead(ctx);
} else { } else {
int numSettings = payloadLength / SETTING_ENTRY_LENGTH; int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
Http2Settings settings = new Http2Settings(5); Http2Settings settings = new Http2Settings();
for (int index = 0; index < numSettings; ++index) { for (int index = 0; index < numSettings; ++index) {
int id = payload.readUnsignedShort(); int id = payload.readUnsignedShort();
long value = payload.readUnsignedInt(); long value = payload.readUnsignedInt();
try {
settings.put(id, value); 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); observer.onSettingsRead(ctx, settings);
// Provide an interface for non-observers to capture 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) private static void verifyStreamOrConnectionId(int streamId, String argumentName)
throws Http2Exception { throws Http2Exception {
if (streamId < 0) { if (streamId < 0) {
throw protocolError("%s must be >= 0", argumentName); 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; 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_HEADER_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_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_BYTE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT; 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.MAX_WEIGHT;
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_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.PRIORITY_ENTRY_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_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.writeFrameHeader;
import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt; import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt;
import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort; import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort;
@ -50,6 +51,7 @@ import io.netty.util.collection.IntObjectMap;
public class DefaultHttp2FrameWriter implements Http2FrameWriter { public class DefaultHttp2FrameWriter implements Http2FrameWriter {
private final Http2HeadersEncoder headersEncoder; private final Http2HeadersEncoder headersEncoder;
private int maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
public DefaultHttp2FrameWriter() { public DefaultHttp2FrameWriter() {
this(new DefaultHttp2HeadersEncoder()); this(new DefaultHttp2HeadersEncoder());
@ -69,6 +71,29 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
return headersEncoder.maxHeaderTableSize(); 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 @Override
public void close() { public void close() {
// Nothing to do. // Nothing to do.
@ -76,14 +101,12 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
@Override @Override
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
ByteBuf data, int padding, boolean endStream, boolean endSegment) { ByteBuf data, int padding, boolean endStream) {
try { try {
verifyStreamId(streamId, "Stream ID"); verifyStreamId(streamId, "Stream ID");
verifyPadding(padding); verifyPadding(padding);
Http2Flags flags = Http2Flags flags = new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream);
new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream)
.endOfSegment(endSegment);
int payloadLength = data.readableBytes() + padding + flags.getPaddingPresenceFieldLength(); int payloadLength = data.readableBytes() + padding + flags.getPaddingPresenceFieldLength();
verifyPayloadLength(payloadLength); verifyPayloadLength(payloadLength);
@ -109,17 +132,17 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
@Override @Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, 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, return writeHeadersInternal(ctx, promise, streamId, headers, padding, endStream,
endSegment, false, 0, (short) 0, false); false, 0, (short) 0, false);
} }
@Override @Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise,
int streamId, Http2Headers headers, int streamDependency, short weight, 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, return writeHeadersInternal(ctx, promise, streamId, headers, padding, endStream,
endSegment, true, streamDependency, weight, exclusive); true, streamDependency, weight, exclusive);
} }
@Override @Override
@ -227,7 +250,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0); Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
int promisedStreamIdLength = INT_FIELD_LENGTH; int promisedStreamIdLength = INT_FIELD_LENGTH;
int nonFragmentLength = promisedStreamIdLength + padding + flags.getPaddingPresenceFieldLength(); int nonFragmentLength = promisedStreamIdLength + padding + flags.getPaddingPresenceFieldLength();
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength; int maxFragmentLength = maxFrameSize - nonFragmentLength;
ByteBuf fragment = ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
@ -317,7 +340,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
} }
private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx, ChannelPromise promise, 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) { boolean hasPriority, int streamDependency, short weight, boolean exclusive) {
ByteBuf headerBlock = null; ByteBuf headerBlock = null;
try { try {
@ -333,13 +356,12 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
headersEncoder.encodeHeaders(headers, headerBlock); headersEncoder.encodeHeaders(headers, headerBlock);
Http2Flags flags = Http2Flags flags =
new Http2Flags().endOfStream(endStream).endOfSegment(endSegment) new Http2Flags().endOfStream(endStream).priorityPresent(hasPriority).paddingPresent(padding > 0);
.priorityPresent(hasPriority).paddingPresent(padding > 0);
// Read the first fragment (possibly everything). // Read the first fragment (possibly everything).
int nonFragmentBytes = int nonFragmentBytes =
padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength(); padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength();
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentBytes; int maxFragmentLength = maxFrameSize - nonFragmentBytes;
ByteBuf fragment = ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); 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 * Drains the header block and creates a composite buffer containing the first frame and a
* number of CONTINUATION frames. * 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) { int streamId, int padding, ByteBuf headerBlock, ByteBuf firstFrame) {
// Create a composite buffer wrapping the first frame and any continuation frames. // Create a composite buffer wrapping the first frame and any continuation frames.
CompositeByteBuf out = ctx.alloc().compositeBuffer(); 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 * Allocates a new buffer and writes a single continuation frame with a fragment of the header
* block to the output buffer. * block to the output buffer.
*/ */
private static ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId, private ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId,
ByteBuf headerBlock, int padding) { ByteBuf headerBlock, int padding) {
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0); Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength(); int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength();
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength; int maxFragmentLength = maxFrameSize - nonFragmentLength;
ByteBuf fragment = ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
@ -466,8 +488,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
} }
} }
private static void verifyPayloadLength(int payloadLength) { private void verifyPayloadLength(int payloadLength) {
if (payloadLength > MAX_FRAME_PAYLOAD_LENGTH) { if (payloadLength > maxFrameSize) {
throw new IllegalArgumentException("Total payload length " + payloadLength throw new IllegalArgumentException("Total payload length " + payloadLength
+ " exceeds max frame length."); + " exceeds max frame length.");
} }

View File

@ -15,6 +15,10 @@
package io.netty.handler.codec.http2; 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.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -33,10 +37,12 @@ public final class DefaultHttp2Headers extends Http2Headers {
private final HeaderEntry[] entries; private final HeaderEntry[] entries;
private final HeaderEntry head; private final HeaderEntry head;
private final int size;
private DefaultHttp2Headers(Builder builder) { private DefaultHttp2Headers(Builder builder) {
entries = builder.entries; entries = builder.entries;
head = builder.head; head = builder.head;
size = builder.size;
} }
@Override @Override
@ -97,7 +103,12 @@ public final class DefaultHttp2Headers extends Http2Headers {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return head == head.after; return size == 0;
}
@Override
public int size() {
return size;
} }
@Override @Override
@ -131,11 +142,16 @@ public final class DefaultHttp2Headers extends Http2Headers {
private HeaderEntry[] entries; private HeaderEntry[] entries;
private HeaderEntry head; private HeaderEntry head;
private Http2Headers buildResults; private Http2Headers buildResults;
private int size;
public Builder() { public Builder() {
clear(); clear();
} }
/**
* Clears all existing headers from this collection and replaces them with the given header
* set.
*/
public void set(Http2Headers headers) { public void set(Http2Headers headers) {
// No need to lazy copy the previous results, since we're starting from scratch. // No need to lazy copy the previous results, since we're starting from scratch.
clear(); 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) { 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 // If this is the first call on the builder since the last build, copy the previous
// results. // results.
@ -159,6 +180,9 @@ public final class DefaultHttp2Headers extends Http2Headers {
return this; return this;
} }
/**
* Removes the header with the given name from this collection.
*/
public Builder remove(final String name) { public Builder remove(final String name) {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
@ -175,6 +199,11 @@ public final class DefaultHttp2Headers extends Http2Headers {
return this; 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) { 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 // If this is the first call on the builder since the last build, copy the previous
// results. // results.
@ -191,6 +220,11 @@ public final class DefaultHttp2Headers extends Http2Headers {
return this; 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) { public Builder set(final String name, final Iterable<?> values) {
if (values == null) { if (values == null) {
throw new NullPointerException("values"); throw new NullPointerException("values");
@ -218,48 +252,52 @@ public final class DefaultHttp2Headers extends Http2Headers {
return this; return this;
} }
/**
* Clears all values from this collection.
*/
public Builder clear() { public Builder clear() {
// No lazy copy required, since we're just creating a new array. // No lazy copy required, since we're just creating a new array.
entries = new HeaderEntry[BUCKET_SIZE]; entries = new HeaderEntry[BUCKET_SIZE];
head = new HeaderEntry(-1, null, null); head = new HeaderEntry(-1, null, null);
head.before = head.after = head; head.before = head.after = head;
buildResults = null; buildResults = null;
size = 0;
return this; return this;
} }
/** /**
* Sets the {@link HttpName#METHOD} header. * Sets the {@link PseudoHeaderName#METHOD} header.
*/ */
public Builder method(String method) { 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) { 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) { 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) { 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) { 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. // Update the linked list.
newEntry.addBefore(head); newEntry.addBefore(head);
size++;
} }
private void remove0(int hash, int hashTableIndex, String name) { private void remove0(int hash, int hashTableIndex, String name) {
@ -310,6 +349,7 @@ public final class DefaultHttp2Headers extends Http2Headers {
for (;;) { for (;;) {
if (e.hash == hash && eq(name, e.key)) { if (e.hash == hash && eq(name, e.key)) {
e.remove(); e.remove();
size--;
HeaderEntry next = e.next; HeaderEntry next = e.next;
if (next != null) { if (next != null) {
entries[hashTableIndex] = next; entries[hashTableIndex] = next;
@ -331,6 +371,7 @@ public final class DefaultHttp2Headers extends Http2Headers {
if (next.hash == hash && eq(name, next.key)) { if (next.hash == hash && eq(name, next.key)) {
e.next = next.next; e.next = next.next;
next.remove(); next.remove();
size--;
} else { } else {
e = next; 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) { private static void validateHeaderName(String name) {
if (name == null) { if (name == null) {
@ -383,6 +424,13 @@ public final class DefaultHttp2Headers extends Http2Headers {
throw new IllegalArgumentException("name contains non-ascii character: " + name); 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_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_HEADER_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.Http2Error.COMPRESSION_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.util.CharsetUtil.UTF_8; import static io.netty.util.CharsetUtil.UTF_8;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufInputStream;
@ -30,6 +31,7 @@ import com.twitter.hpack.HeaderListener;
public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder { public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder {
private final Decoder decoder; private final Decoder decoder;
private int maxHeaderListSize = Integer.MAX_VALUE;
public DefaultHttp2HeadersDecoder() { public DefaultHttp2HeadersDecoder() {
this(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE); this(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE);
@ -49,26 +51,52 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder {
return decoder.getMaxHeaderTableSize(); 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 @Override
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception { public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
try { try {
final DefaultHttp2Headers.Builder headersBuilder = new DefaultHttp2Headers.Builder(); final DefaultHttp2Headers.Builder headersBuilder = new DefaultHttp2Headers.Builder();
HeaderListener listener = new HeaderListener() { HeaderListener listener = new HeaderListener() {
@Override @Override
public void emitHeader(byte[] key, byte[] value, boolean sensitive) { public void addHeader(byte[] key, byte[] value, boolean sensitive) {
headersBuilder.add(new String(key, UTF_8), new String(value, UTF_8)); String keyString = new String(key, UTF_8);
String valueString = new String(value, UTF_8);
headersBuilder.add(keyString, valueString);
} }
}; };
decoder.decode(new ByteBufInputStream(headerBlock), listener); decoder.decode(new ByteBufInputStream(headerBlock), listener);
boolean truncated = decoder.endHeaderBlock(listener); boolean truncated = decoder.endHeaderBlock();
if (truncated) { if (truncated) {
// TODO: what's the right thing to do here? // 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) { } catch (IOException e) {
throw new Http2Exception(COMPRESSION_ERROR, e.getMessage()); 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; 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_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 static io.netty.util.CharsetUtil.UTF_8;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufOutputStream;
@ -23,25 +25,36 @@ import io.netty.buffer.Unpooled;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import com.twitter.hpack.Encoder; import com.twitter.hpack.Encoder;
public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder { public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder {
private final Encoder encoder; private final Encoder encoder;
private final ByteBuf tableSizeChangeOutput = Unpooled.buffer(); 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() { 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); encoder = new Encoder(maxHeaderTableSize);
this.sensitiveHeaders.addAll(sensitiveHeaders);
} }
@Override @Override
public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception { public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception {
try { 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 // If there was a change in the table size, serialize the output from the encoder
// resulting from that change. // resulting from that change.
if (tableSizeChangeOutput.isReadable()) { if (tableSizeChangeOutput.isReadable()) {
@ -50,12 +63,19 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder {
} }
OutputStream stream = new ByteBufOutputStream(buffer); OutputStream stream = new ByteBufOutputStream(buffer);
for (Entry<String, String> header : headers) { // Write pseudo headers first as required by the HTTP/2 spec.
byte[] key = header.getKey().getBytes(UTF_8); for (Http2Headers.PseudoHeaderName pseudoHeader : Http2Headers.PseudoHeaderName.values()) {
byte[] value = header.getValue().getBytes(UTF_8); String name = pseudoHeader.value();
encoder.encodeHeader(stream, key, value, false); 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) { } catch (IOException e) {
throw Http2Exception.format(Http2Error.COMPRESSION_ERROR, throw Http2Exception.format(Http2Error.COMPRESSION_ERROR,
"Failed encoding headers block: %s", e.getMessage()); "Failed encoding headers block: %s", e.getMessage());
@ -77,4 +97,21 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder {
return encoder.getMaxHeaderTableSize(); 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 @Override
public void applyInboundFlowControl(int streamId, ByteBuf data, int padding, public void applyInboundFlowControl(int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, FrameWriter frameWriter) boolean endOfStream, FrameWriter frameWriter)
throws Http2Exception { throws Http2Exception {
int dataLength = data.readableBytes(); int dataLength = data.readableBytes();
applyConnectionFlowControl(dataLength, frameWriter); applyConnectionFlowControl(dataLength, frameWriter);

View File

@ -149,13 +149,16 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
@Override @Override
public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream, 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 state = stateOrFail(streamId);
OutboundFlowState.Frame frame = 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(); int dataLength = data.readableBytes();
if (state.writableWindow() >= dataLength) { if (window >= dataLength) {
// Window size is large enough to send entire data frame // Window size is large enough to send entire data frame
frame.write(); frame.write();
return; return;
@ -164,13 +167,13 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
// Enqueue the frame to be written when the window size permits. // Enqueue the frame to be written when the window size permits.
frame.enqueue(); frame.enqueue();
if (state.writableWindow() <= 0) { if (window <= 0) {
// Stream is stalled, don't send anything now. // Stream is stalled, don't send anything now.
return; return;
} }
// Create and send a partial frame up to the window size. // 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) { 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. * 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, Frame newFrame(ByteBuf data, int padding, boolean endStream, FrameWriter writer) {
FrameWriter writer) { return new Frame(data, padding, endStream, writer);
return new Frame(data, padding, endStream, endSegment, writer);
} }
/** /**
@ -529,16 +531,13 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
private final ByteBuf data; private final ByteBuf data;
private final int padding; private final int padding;
private final boolean endStream; private final boolean endStream;
private final boolean endSegment;
private final FrameWriter writer; private final FrameWriter writer;
private boolean enqueued; private boolean enqueued;
Frame(ByteBuf data, int padding, boolean endStream, boolean endSegment, Frame(ByteBuf data, int padding, boolean endStream, FrameWriter writer) {
FrameWriter writer) {
this.data = data; this.data = data;
this.padding = padding; this.padding = padding;
this.endStream = endStream; this.endStream = endStream;
this.endSegment = endSegment;
this.writer = writer; this.writer = writer;
} }
@ -575,11 +574,25 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
* priority tree. * priority tree.
*/ */
void write() throws Http2Exception { void write() throws Http2Exception {
int dataLength = data.readableBytes(); // Using a do/while loop because if the buffer is empty we still need to call
connectionState().incrementStreamWindow(-dataLength); // the writer once to send the empty frame.
incrementStreamWindow(-dataLength); do {
writer.writeFrame(stream.id(), data, padding, endStream, endSegment); int bytesToWrite = data.readableBytes();
decrementPendingBytes(dataLength); 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) { Frame split(int maxBytes) {
// TODO: Should padding be included in the chunks or only the last frame? // TODO: Should padding be included in the chunks or only the last frame?
maxBytes = min(maxBytes, data.readableBytes()); 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); decrementPendingBytes(maxBytes);
return frame; return frame;
} }

View File

@ -53,22 +53,22 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
@Override @Override
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
ByteBuf data, int padding, boolean endStream, boolean endSegment) { ByteBuf data, int padding, boolean endStream) {
return super.writeData(ctx, promise, streamId, data, padding, endStream, endSegment); return super.writeData(ctx, promise, streamId, data, padding, endStream);
} }
@Override @Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, 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 super.writeHeaders(ctx, promise, streamId, headers, padding, endStream, endSegment); return super.writeHeaders(ctx, promise, streamId, headers, padding, endStream);
} }
@Override @Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise,
int streamId, Http2Headers headers, int streamDependency, short weight, 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, return super.writeHeaders(ctx, promise, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream);
} }
@Override @Override
@ -102,16 +102,16 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment) throws Http2Exception { boolean endOfStream) throws Http2Exception {
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment); observer.onDataRead(ctx, streamId, data, padding, endOfStream);
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
boolean endSegment) throws Http2Exception { throws Http2Exception {
observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive,
padding, endStream, endSegment); padding, endStream);
} }
@Override @Override

View File

@ -177,10 +177,10 @@ public class DelegatingHttp2HttpConnectionHandler extends DelegatingHttp2Connect
ChannelPromise headerPromise = ctx.newPromise(); ChannelPromise headerPromise = ctx.newPromise();
ChannelPromise dataPromise = ctx.newPromise(); ChannelPromise dataPromise = ctx.newPromise();
promiseAggregator.add(headerPromise, dataPromise); promiseAggregator.add(headerPromise, dataPromise);
writeHeaders(ctx, headerPromise, streamId, http2Headers.build(), 0, false, false); writeHeaders(ctx, headerPromise, streamId, http2Headers.build(), 0, false);
writeData(ctx, dataPromise, streamId, httpMsg.content(), 0, true, true); writeData(ctx, dataPromise, streamId, httpMsg.content(), 0, true);
} else { } else {
writeHeaders(ctx, promise, streamId, http2Headers.build(), 0, true, true); writeHeaders(ctx, promise, streamId, http2Headers.build(), 0, true);
} }
} else { } else {
ctx.write(msg, promise); 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 static io.netty.util.CharsetUtil.UTF_8;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action; 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[] 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 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 CONNECTION_STREAM_ID = 0;
public static final int HTTP_UPGRADE_STREAM_ID = 1; 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_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 int PING_FRAME_PAYLOAD_LENGTH = 8;
public static final short MAX_UNSIGNED_BYTE = 0xFF; public static final short MAX_UNSIGNED_BYTE = 0xFF;
public static final int MAX_UNSIGNED_SHORT = 0xFFFF; public static final int MAX_UNSIGNED_SHORT = 0xFFFF;
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL; public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
public static final int FRAME_HEADER_LENGTH = 8; public static final int FRAME_HEADER_LENGTH = 9;
public static final int FRAME_LENGTH_MASK = 0x3FFF;
public static final int SETTING_ENTRY_LENGTH = 6; public static final int SETTING_ENTRY_LENGTH = 6;
public static final int PRIORITY_ENTRY_LENGTH = 5; public static final int PRIORITY_ENTRY_LENGTH = 5;
public static final int INT_FIELD_LENGTH = 4; 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_ENABLE_PUSH = 2;
public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3; public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4; 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 int DEFAULT_WINDOW_SIZE = 65535;
public static final boolean DEFAULT_ENABLE_PUSH = true; public static final boolean DEFAULT_ENABLE_PUSH = true;
public static final short DEFAULT_PRIORITY_WEIGHT = 16; public static final short DEFAULT_PRIORITY_WEIGHT = 16;
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096; public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
public static final int DEFAULT_MAX_HEADER_SIZE = 8192; 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}. * 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. * 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, public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
Http2Flags flags, int streamId) { Http2Flags flags, int streamId) {
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength); out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength); out.writeMedium(payloadLength);
out.writeByte(type); out.writeByte(type);
out.writeByte(flags.value()); out.writeByte(flags.value());
out.writeInt(streamId); out.writeInt(streamId);
@ -181,6 +206,21 @@ public final class Http2CodecUtil {
throw cause; 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() { private Http2CodecUtil() {
} }
} }

View File

@ -20,7 +20,6 @@ package io.netty.handler.codec.http2;
*/ */
public final class Http2Flags { public final class Http2Flags {
public static final short END_STREAM = 0x1; 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 END_HEADERS = 0x4;
public static final short ACK = 0x1; public static final short ACK = 0x1;
public static final short PADDED = 0x8; public static final short PADDED = 0x8;
@ -50,14 +49,6 @@ public final class Http2Flags {
return isFlagSet(END_STREAM); 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, * Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
* PUSH_PROMISE, and CONTINUATION frames. * PUSH_PROMISE, and CONTINUATION frames.
@ -113,13 +104,6 @@ public final class Http2Flags {
return setFlag(endOfStream, END_STREAM); 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. * Sets the {@link #END_HEADERS} flag.
*/ */
@ -211,9 +195,6 @@ public final class Http2Flags {
if (priorityPresent()) { if (priorityPresent()) {
builder.append("PRIORITY_PRESENT,"); builder.append("PRIORITY_PRESENT,");
} }
if (endOfSegment()) {
builder.append("END_OF_SEGMENT,");
}
if (paddingPresent()) { if (paddingPresent()) {
builder.append("PADDING_PRESENT,"); builder.append("PADDING_PRESENT,");
} }

View File

@ -24,18 +24,18 @@ public class Http2FrameAdapter implements Http2FrameObserver {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment) throws Http2Exception { boolean endOfStream) throws Http2Exception {
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, boolean endSegment) throws Http2Exception { int padding, boolean endStream) throws Http2Exception {
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
boolean endSegment) throws Http2Exception { throws Http2Exception {
} }
@Override @Override

View File

@ -51,25 +51,24 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
} }
public void logData(Direction direction, int streamId, ByteBuf data, int padding, public void logData(Direction direction, int streamId, ByteBuf data, int padding,
boolean endStream, boolean endSegment) { boolean endStream) {
log(direction, log(direction,
"DATA: streamId=%d, padding=%d, endStream=%b, endSegment=%b, length=%d, bytes=%s", "DATA: streamId=%d, padding=%d, endStream=%b, length=%d, bytes=%s",
streamId, padding, endStream, endSegment, data.readableBytes(), ByteBufUtil.hexDump(data)); streamId, padding, endStream, data.readableBytes(), ByteBufUtil.hexDump(data));
} }
public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding, public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding,
boolean endStream, boolean endSegment) { boolean endStream) {
log(direction, "HEADERS: streamId:%d, headers=%s, padding=%d, endStream=%b, endSegment=%b", log(direction, "HEADERS: streamId:%d, headers=%s, padding=%d, endStream=%b",
streamId, headers, padding, endStream, endSegment); streamId, headers, padding, endStream);
} }
public void logHeaders(Direction direction, int streamId, Http2Headers headers, public void logHeaders(Direction direction, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) {
boolean endSegment) {
log(direction, log(direction,
"HEADERS: streamId:%d, headers=%s, streamDependency=%d, weight=%d, exclusive=%b, " "HEADERS: streamId:%d, headers=%s, streamDependency=%d, weight=%d, exclusive=%b, "
+ "padding=%d, endStream=%b, endSegment=%b", streamId, headers, + "padding=%d, endStream=%b", streamId, headers,
streamDependency, weight, exclusive, padding, endStream, endSegment); streamDependency, weight, exclusive, padding, endStream);
} }
public void logPriority(Direction direction, int streamId, int streamDependency, short weight, 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 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 * @param endOfStream Indicates whether this is the last frame to be sent from the remote
* endpoint for this stream. * 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, 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. * 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 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 * @param endStream Indicates whether this is the last frame to be sent from the remote endpoint
* for this stream. * 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, 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. * 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 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 * @param endStream Indicates whether this is the last frame to be sent from the remote endpoint
* for this stream. * for this stream.
* @param endSegment Indicates whether this frame is the end of the current segment.
*/ */
void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
boolean endSegment) throws Http2Exception; throws Http2Exception;
/** /**
* Handles an inbound PRIORITY frame. * Handles an inbound PRIORITY frame.

View File

@ -43,6 +43,26 @@ public interface Http2FrameReader extends Closeable {
*/ */
long maxHeaderTableSize(); 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. * 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 data the payload of the frame.
* @param padding the amount of padding to be added to the end 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 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. * @return the future for the write.
*/ */
ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, 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. * 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 headers the headers to be sent.
* @param padding the amount of padding to be added to the end 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 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. * @return the future for the write.
*/ */
ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, 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. * 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 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 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 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. * @return the future for the write.
*/ */
ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
Http2Headers headers, int streamDependency, short weight, boolean exclusive, 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. * 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. * Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
*/ */
long maxHeaderTableSize(); 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; return true;
} }
@Override
public int size() {
return 0;
}
@Override @Override
public Set<String> names() { public Set<String> names() {
return Collections.emptySet(); 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}. * {@code :method}.
*/ */
METHOD(":method"), METHOD(PSEUDO_HEADER_PREFIX + "method"),
/** /**
* {@code :scheme}. * {@code :scheme}.
*/ */
SCHEME(":scheme"), SCHEME(PSEUDO_HEADER_PREFIX + "scheme"),
/** /**
* {@code :authority}. * {@code :authority}.
*/ */
AUTHORITY(":authority"), AUTHORITY(PSEUDO_HEADER_PREFIX + "authority"),
/** /**
* {@code :path}. * {@code :path}.
*/ */
PATH(":path"), PATH(PSEUDO_HEADER_PREFIX + "path"),
/** /**
* {@code :status}. * {@code :status}.
*/ */
STATUS(":status"); STATUS(PSEUDO_HEADER_PREFIX + "status");
private final String value; private final String value;
HttpName(String value) { PseudoHeaderName(String value) {
this.value = value; this.value = value;
} }
public String value() { public String value() {
return 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(); 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() { 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() { 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() { 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() { 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() { public final String status() {
return get(HttpName.STATUS.value()); return get(PseudoHeaderName.STATUS.value());
} }
@Override @Override

View File

@ -36,4 +36,14 @@ public interface Http2HeadersDecoder {
* Gets the maximum header table size for this decoder. * Gets the maximum header table size for this decoder.
*/ */
int maxHeaderTableSize(); 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. * Gets the current maximum value for the header table size.
*/ */
int maxHeaderTableSize(); 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 data the data portion of the data frame. Does not contain padding.
* @param padding the amount of padding received in the original frame. * @param padding the amount of padding received in the original frame.
* @param endOfStream indicates whether this is the last frame for the stream. * @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. * @param frameWriter allows this flow controller to send window updates to the remote endpoint.
* @throws Http2Exception thrown if any protocol-related error occurred. * @throws Http2Exception thrown if any protocol-related error occurred.
*/ */
void applyInboundFlowControl(int streamId, ByteBuf data, int padding, boolean endOfStream, 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 @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, boolean endOfSegment) int padding, boolean endOfStream)
throws Http2Exception { throws Http2Exception {
logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment); logger.logData(INBOUND, streamId, data, padding, endOfStream);
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment); observer.onDataRead(ctx, streamId, data, padding, endOfStream);
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, boolean endSegment) Http2Headers headers, int padding, boolean endStream)
throws Http2Exception { throws Http2Exception {
logger.logHeaders(INBOUND, streamId, headers, padding, endStream, endSegment); logger.logHeaders(INBOUND, streamId, headers, padding, endStream);
observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); observer.onHeadersRead(ctx, streamId, headers, padding, endStream);
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, boolean exclusive, 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, logger.logHeaders(INBOUND, streamId, headers, streamDependency, weight, exclusive,
padding, endStream, endSegment); padding, endStream);
observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive,
padding, endStream, endSegment); padding, endStream);
} }
@Override @Override
@ -154,4 +154,23 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
return reader.maxHeaderTableSize(); 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; 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.buffer.ByteBuf;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -40,7 +41,7 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
public enum SelectedProtocol { public enum SelectedProtocol {
/** Must be updated to match the HTTP/2 draft number. */ /** 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_1("http/1.1"),
HTTP_1_0("http/1.0"), HTTP_1_0("http/1.0"),
UNKNOWN("Unknown"); UNKNOWN("Unknown");

View File

@ -30,14 +30,18 @@ public interface Http2OutboundFlowController {
/** /**
* Writes a single data frame to the remote endpoint. * Writes a single data frame to the remote endpoint.
*/ */
void writeFrame(int streamId, ByteBuf data, int padding, boolean endStream, void writeFrame(int streamId, ByteBuf data, int padding, boolean endStream);
boolean endSegment);
/** /**
* Called if an error occurred before the write could take place. Sets the failure on the * Called if an error occurred before the write could take place. Sets the failure on the
* channel promise. * channel promise.
*/ */
void setFailure(Throwable cause); 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 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 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 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. * @param frameWriter peforms to the write of the frame to the remote endpoint.
* @throws Http2Exception thrown if a protocol-related error occurred. * @throws Http2Exception thrown if a protocol-related error occurred.
*/ */
void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream, 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 @Override
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
ByteBuf data, int padding, boolean endStream, boolean endSegment) { ByteBuf data, int padding, boolean endStream) {
logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment); logger.logData(OUTBOUND, streamId, data, padding, endStream);
return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment); return writer.writeData(ctx, promise, streamId, data, padding, endStream);
} }
@Override @Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, 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) {
logger.logHeaders(OUTBOUND, streamId, headers, padding, endStream, endSegment); logger.logHeaders(OUTBOUND, streamId, headers, padding, endStream);
return writer.writeHeaders(ctx, promise, streamId, headers, padding, endStream, endSegment); return writer.writeHeaders(ctx, promise, streamId, headers, padding, endStream);
} }
@Override @Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise, public ChannelFuture writeHeaders(ChannelHandlerContext ctx, ChannelPromise promise,
int streamId, Http2Headers headers, int streamDependency, short weight, 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, logger.logHeaders(OUTBOUND, streamId, headers, streamDependency, weight, exclusive,
padding, endStream, endSegment); padding, endStream);
return writer.writeHeaders(ctx, promise, streamId, headers, streamDependency, weight, return writer.writeHeaders(ctx, promise, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream);
} }
@Override @Override
@ -140,4 +140,24 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
public long maxHeaderTableSize() { public long maxHeaderTableSize() {
return writer.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_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_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_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; import io.netty.util.collection.IntObjectHashMap;
/** /**
@ -30,6 +33,7 @@ import io.netty.util.collection.IntObjectHashMap;
public final class Http2Settings extends IntObjectHashMap<Long> { public final class Http2Settings extends IntObjectHashMap<Long> {
public Http2Settings() { public Http2Settings() {
this(6 /* number of standard settings */);
} }
public Http2Settings(int initialCapacity, float loadFactor) { public Http2Settings(int initialCapacity, float loadFactor) {
@ -40,21 +44,37 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
super(initialCapacity); super(initialCapacity);
} }
/**
* Overrides the superclass method to perform verification of standard HTTP/2 settings.
*
* @throws IllegalArgumentException if verification of the setting fails.
*/
@Override @Override
public Long put(int key, Long value) { public Long put(int key, Long value) {
verifyStandardSetting(key, value); verifyStandardSetting(key, value);
return super.put(key, value); return super.put(key, value);
} }
/**
* Gets the {@code SETTINGS_HEADER_TABLE_SIZE} value. If unavailable, returns {@code null}.
*/
public Long headerTableSize() { public Long headerTableSize() {
return get(SETTINGS_HEADER_TABLE_SIZE); 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) { public Http2Settings headerTableSize(long value) {
put(SETTINGS_HEADER_TABLE_SIZE, value); put(SETTINGS_HEADER_TABLE_SIZE, value);
return this; return this;
} }
/**
* Gets the {@code SETTINGS_ENABLE_PUSH} value. If unavailable, returns {@code null}.
*/
public Boolean pushEnabled() { public Boolean pushEnabled() {
Long value = get(SETTINGS_ENABLE_PUSH); Long value = get(SETTINGS_ENABLE_PUSH);
if (value == null) { if (value == null) {
@ -63,39 +83,99 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
return value != 0L; return value != 0L;
} }
/**
* Sets the {@code SETTINGS_ENABLE_PUSH} value.
*/
public Http2Settings pushEnabled(boolean enabled) { public Http2Settings pushEnabled(boolean enabled) {
put(SETTINGS_ENABLE_PUSH, enabled ? 1L : 0L); put(SETTINGS_ENABLE_PUSH, enabled ? 1L : 0L);
return this; return this;
} }
/**
* Gets the {@code SETTINGS_MAX_CONCURRENT_STREAMS} value. If unavailable, returns {@code null}.
*/
public Long maxConcurrentStreams() { public Long maxConcurrentStreams() {
return get(SETTINGS_MAX_CONCURRENT_STREAMS); 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) { public Http2Settings maxConcurrentStreams(long value) {
put(SETTINGS_MAX_CONCURRENT_STREAMS, value); put(SETTINGS_MAX_CONCURRENT_STREAMS, value);
return this; return this;
} }
/**
* Gets the {@code SETTINGS_INITIAL_WINDOW_SIZE} value. If unavailable, returns {@code null}.
*/
public Integer initialWindowSize() { public Integer initialWindowSize() {
Long value = get(SETTINGS_INITIAL_WINDOW_SIZE); return getIntValue(SETTINGS_INITIAL_WINDOW_SIZE);
if (value == null) {
return null;
}
return value.intValue();
} }
/**
* Sets the {@code SETTINGS_INITIAL_WINDOW_SIZE} value.
*
* @throws IllegalArgumentException if verification of the setting fails.
*/
public Http2Settings initialWindowSize(int value) { public Http2Settings initialWindowSize(int value) {
put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value); put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value);
return this; 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) { public Http2Settings copyFrom(Http2Settings settings) {
clear(); clear();
putAll(settings); putAll(settings);
return this; 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) { private void verifyStandardSetting(int key, Long value) {
if (value == null) { if (value == null) {
throw new NullPointerException("value"); throw new NullPointerException("value");
@ -103,7 +183,8 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
switch (key) { switch (key) {
case SETTINGS_HEADER_TABLE_SIZE: case SETTINGS_HEADER_TABLE_SIZE:
if (value < 0L || value > MAX_UNSIGNED_INT) { 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; break;
case SETTINGS_ENABLE_PUSH: case SETTINGS_ENABLE_PUSH:
@ -113,12 +194,26 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
break; break;
case SETTINGS_MAX_CONCURRENT_STREAMS: case SETTINGS_MAX_CONCURRENT_STREAMS:
if (value < 0L || value > MAX_UNSIGNED_INT) { 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; break;
case SETTINGS_INITIAL_WINDOW_SIZE: case SETTINGS_INITIAL_WINDOW_SIZE:
if (value < 0L || value > Integer.MAX_VALUE) { 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; 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.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.collection.IntObjectMap;
import io.netty.util.collection.IntObjectHashMap; 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.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
@ -62,16 +59,16 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
HEADERS_TO_EXCLUDE = new HashSet<String>(); HEADERS_TO_EXCLUDE = new HashSet<String>();
HEADER_NAME_TRANSLATIONS_REQUEST = new HashMap<String, String>(); HEADER_NAME_TRANSLATIONS_REQUEST = new HashMap<String, String>();
HEADER_NAME_TRANSLATIONS_RESPONSE = 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()); 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()); 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()); Http2HttpHeaders.Names.SCHEME.toString());
HEADER_NAME_TRANSLATIONS_REQUEST.putAll(HEADER_NAME_TRANSLATIONS_RESPONSE); 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()); Http2HttpHeaders.Names.PATH.toString());
} }
@ -155,8 +152,8 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
} }
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfSegment) throws Http2Exception { boolean endOfStream) throws Http2Exception {
// Padding is already stripped out of data by super class // Padding is already stripped out of data by super class
Http2HttpMessageAccumulator msgAccumulator = getMessage(streamId); Http2HttpMessageAccumulator msgAccumulator = getMessage(streamId);
if (msgAccumulator == null) { if (msgAccumulator == null) {
@ -241,7 +238,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, 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); Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(streamId, headers, true);
processHeadersEnd(ctx, streamId, msgAccumulator, endOfStream); processHeadersEnd(ctx, streamId, msgAccumulator, endOfStream);
} }
@ -270,8 +267,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
short weight, boolean exclusive, int padding, boolean endOfStream, boolean endSegment) short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
throws Http2Exception {
Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(streamId, headers, true); Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(streamId, headers, true);
try { try {
setDependencyHeaders(msgAccumulator, streamDependency, weight, exclusive); 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, public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers,
int padding) throws Http2Exception { int padding) throws Http2Exception {
// Do not allow adding of headers to existing Http2HttpMessageAccumulator // 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 // be a CONTINUATION frame for more headers
Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(promisedStreamId, headers, false); Http2HttpMessageAccumulator msgAccumulator = processHeadersBegin(promisedStreamId, headers, false);
if (msgAccumulator == null) { if (msgAccumulator == null) {
@ -574,7 +570,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
throw new IllegalStateException("Headers object is null"); 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 // All headers that start with ':' are only valid in HTTP/2 context
Iterator<Entry<String, String>> itr = http2Headers.iterator(); Iterator<Entry<String, String>> itr = http2Headers.iterator();
while (itr.hasNext()) { while (itr.hasNext()) {

View File

@ -67,33 +67,33 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void emptyDataShouldRoundtrip() throws Exception { public void emptyDataShouldRoundtrip() throws Exception {
ByteBuf data = Unpooled.EMPTY_BUFFER; 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@Test @Test
public void dataShouldRoundtrip() throws Exception { public void dataShouldRoundtrip() throws Exception {
ByteBuf data = dummyData(); 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@Test @Test
public void dataWithPaddingShouldRoundtrip() throws Exception { public void dataWithPaddingShouldRoundtrip() throws Exception {
ByteBuf data = dummyData(); 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@ -197,84 +197,84 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void emptyHeadersShouldRoundtrip() throws Exception { public void emptyHeadersShouldRoundtrip() throws Exception {
Http2Headers headers = Http2Headers.EMPTY_HEADERS; 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@Test @Test
public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception { public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception {
Http2Headers headers = Http2Headers.EMPTY_HEADERS; 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@Test @Test
public void headersWithoutPriorityShouldRoundtrip() throws Exception { public void headersWithoutPriorityShouldRoundtrip() throws Exception {
Http2Headers headers = dummyHeaders(); Http2Headers headers = dummyHeaders();
writer.writeHeaders(ctx, promise, 1, headers, 0, true, true); writer.writeHeaders(ctx, promise, 1, headers, 0, true);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@Test @Test
public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception { public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception {
Http2Headers headers = dummyHeaders(); Http2Headers headers = dummyHeaders();
writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true); writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@Test @Test
public void headersWithPriorityShouldRoundtrip() throws Exception { public void headersWithPriorityShouldRoundtrip() throws Exception {
Http2Headers headers = dummyHeaders(); 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), 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(); frame.release();
} }
@Test @Test
public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception { public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception {
Http2Headers headers = dummyHeaders(); 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF), 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(); frame.release();
} }
@Test @Test
public void continuedHeadersShouldRoundtrip() throws Exception { public void continuedHeadersShouldRoundtrip() throws Exception {
Http2Headers headers = largeHeaders(); 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), 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(); frame.release();
} }
@Test @Test
public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception { public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception {
Http2Headers headers = largeHeaders(); 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(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF), 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(); 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 org.junit.Test;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -39,11 +40,33 @@ public class DefaultHttp2HeadersTest {
.add("a", "3").build(); .add("a", "3").build();
List<String> aValues = headers.getAll("a"); List<String> aValues = headers.getAll("a");
assertEquals(3, aValues.size()); assertEquals(3, aValues.size());
assertEquals(3, headers.size());
assertEquals("1", aValues.get(0)); assertEquals("1", aValues.get(0));
assertEquals("2", aValues.get(1)); assertEquals("2", aValues.get(1));
assertEquals("3", aValues.get(2)); 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) @Test(expected = NoSuchElementException.class)
public void iterateEmptyHeadersShouldThrow() { public void iterateEmptyHeadersShouldThrow() {
Iterator<Map.Entry<String, String>> iterator = Iterator<Map.Entry<String, String>> iterator =
@ -77,4 +100,14 @@ public class DefaultHttp2HeadersTest {
// Make sure we removed them all. // Make sure we removed them all.
assertTrue(headers.isEmpty()); 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 { private void applyFlowControl(int dataSize, boolean endOfStream) throws Http2Exception {
ByteBuf buf = dummyData(dataSize); ByteBuf buf = dummyData(dataSize);
controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false, frameWriter); controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, frameWriter);
buf.release(); buf.release();
} }

View File

@ -18,6 +18,8 @@ package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http2.Http2OutboundFlowController.FrameWriter; import io.netty.handler.codec.http2.Http2OutboundFlowController.FrameWriter;
import io.netty.util.CharsetUtil;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@ -25,6 +27,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import static io.netty.handler.codec.http2.Http2CodecUtil.*; import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -60,17 +63,32 @@ public class DefaultHttp2OutboundFlowControllerTest {
Http2Stream streamD = connection.local().createStream(STREAM_D, false); Http2Stream streamD = connection.local().createStream(STREAM_D, false);
streamC.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false); streamC.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false);
streamD.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false); streamD.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false);
when(frameWriter.maxFrameSize()).thenReturn(Integer.MAX_VALUE);
} }
@Test @Test
public void frameShouldBeSentImmediately() throws Http2Exception { public void frameShouldBeSentImmediately() throws Http2Exception {
ByteBuf data = dummyData(10); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data.slice());
verifyWrite(STREAM_A, data); verifyWrite(STREAM_A, data);
assertEquals(1, data.refCnt()); assertEquals(1, data.refCnt());
data.release(); 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 @Test
public void stalledStreamShouldQueueFrame() throws Http2Exception { public void stalledStreamShouldQueueFrame() throws Http2Exception {
controller.initialOutboundWindowSize(0); controller.initialOutboundWindowSize(0);
@ -105,7 +123,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
controller.initialOutboundWindowSize(0); controller.initialOutboundWindowSize(0);
ByteBuf data = dummyData(10); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data.slice());
verifyNoWrite(STREAM_A); verifyNoWrite(STREAM_A);
// Verify that the entire frame was sent. // Verify that the entire frame was sent.
@ -145,7 +163,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE); .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
ByteBuf data = dummyData(10); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data.slice());
verifyNoWrite(STREAM_A); verifyNoWrite(STREAM_A);
// Verify that the entire frame was sent. // Verify that the entire frame was sent.
@ -186,7 +204,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE); controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE);
ByteBuf data = dummyData(10); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data.slice());
verifyNoWrite(STREAM_A); verifyNoWrite(STREAM_A);
// Verify that the entire frame was sent. // Verify that the entire frame was sent.
@ -542,21 +560,20 @@ public class DefaultHttp2OutboundFlowControllerTest {
} }
private void send(int streamId, ByteBuf data) throws Http2Exception { 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) { 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) { private void verifyNoWrite(int streamId) {
verify(frameWriter, never()).writeFrame(eq(streamId), any(ByteBuf.class), anyInt(), verify(frameWriter, never()).writeFrame(eq(streamId), any(ByteBuf.class), anyInt(),
anyBoolean(), anyBoolean()); anyBoolean());
} }
private void captureWrite(int streamId, ArgumentCaptor<ByteBuf> captor, boolean endStream) { private void captureWrite(int streamId, ArgumentCaptor<ByteBuf> captor, boolean endStream) {
verify(frameWriter).writeFrame(eq(streamId), captor.capture(), eq(0), eq(endStream), verify(frameWriter).writeFrame(eq(streamId), captor.capture(), eq(0), eq(endStream));
eq(false));
} }
private void setPriority(int stream, int parent, int weight, boolean exclusive) private void setPriority(int stream, int parent, int weight, boolean exclusive)
@ -565,8 +582,11 @@ public class DefaultHttp2OutboundFlowControllerTest {
} }
private static ByteBuf dummyData(int size) { private static ByteBuf dummyData(int size) {
String repeatedData = "0123456789";
ByteBuf buffer = Unpooled.buffer(size); ByteBuf buffer = Unpooled.buffer(size);
buffer.writerIndex(size); for (int index = 0; index < size; ++index) {
buffer.writeByte(repeatedData.charAt(index % repeatedData.length()));
}
return buffer; 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.EMPTY_BUFFER;
import static io.netty.buffer.Unpooled.copiedBuffer; import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.buffer.Unpooled.wrappedBuffer; 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.DEFAULT_PRIORITY_WEIGHT;
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf; import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
import static io.netty.handler.codec.http2.Http2CodecUtil.emptyPingBuf; import static io.netty.handler.codec.http2.Http2CodecUtil.emptyPingBuf;
@ -146,10 +147,16 @@ public class DelegatingHttp2ConnectionHandlerTest {
settings.pushEnabled(true); settings.pushEnabled(true);
settings.maxConcurrentStreams(100); settings.maxConcurrentStreams(100);
settings.headerTableSize(200); settings.headerTableSize(200);
settings.maxFrameSize(DEFAULT_MAX_FRAME_SIZE);
settings.maxHeaderListSize(Integer.MAX_VALUE);
when(inboundFlow.initialInboundWindowSize()).thenReturn(10); when(inboundFlow.initialInboundWindowSize()).thenReturn(10);
when(local.allowPushTo()).thenReturn(true); when(local.allowPushTo()).thenReturn(true);
when(remote.maxStreams()).thenReturn(100); when(remote.maxStreams()).thenReturn(100);
when(reader.maxHeaderTableSize()).thenReturn(200L); 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); handler.handlerAdded(ctx);
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings)); verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
@ -246,72 +253,71 @@ public class DelegatingHttp2ConnectionHandlerTest {
@Test @Test
public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception { public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception {
when(remote.isGoAwayReceived()).thenReturn(true); 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), 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 that the event was absorbed and not propagated to the oberver.
verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(),
anyBoolean(), anyBoolean()); anyBoolean());
} }
@Test @Test
public void dataReadWithEndOfStreamShouldCloseRemoteSide() throws Exception { 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), 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(stream).closeRemoteSide();
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true), verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true));
eq(false));
} }
@Test @Test
public void headersReadAfterGoAwayShouldBeIgnored() throws Exception { public void headersReadAfterGoAwayShouldBeIgnored() throws Exception {
when(remote.isGoAwayReceived()).thenReturn(true); 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(remote, never()).createStream(eq(STREAM_ID), eq(false));
// Verify that the event was absorbed and not propagated to the oberver. // Verify that the event was absorbed and not propagated to the oberver.
verify(observer, never()).onHeadersRead(eq(ctx), anyInt(), any(Http2Headers.class), verify(observer, never()).onHeadersRead(eq(ctx), anyInt(), any(Http2Headers.class),
anyInt(), anyBoolean(), anyBoolean()); anyInt(), anyBoolean());
verify(remote, never()).createStream(anyInt(), anyBoolean()); verify(remote, never()).createStream(anyInt(), anyBoolean());
} }
@Test @Test
public void headersReadForUnknownStreamShouldCreateStream() throws Exception { public void headersReadForUnknownStreamShouldCreateStream() throws Exception {
when(remote.createStream(eq(5), eq(false))).thenReturn(stream); 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(remote).createStream(eq(5), eq(false));
verify(observer).onHeadersRead(eq(ctx), eq(5), eq(EMPTY_HEADERS), eq(0), 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 @Test
public void headersReadForUnknownStreamShouldCreateHalfClosedStream() throws Exception { public void headersReadForUnknownStreamShouldCreateHalfClosedStream() throws Exception {
when(remote.createStream(eq(5), eq(true))).thenReturn(stream); 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(remote).createStream(eq(5), eq(true));
verify(observer).onHeadersRead(eq(ctx), eq(5), eq(EMPTY_HEADERS), eq(0), 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 @Test
public void headersReadForPromisedStreamShouldHalfOpenStream() throws Exception { public void headersReadForPromisedStreamShouldHalfOpenStream() throws Exception {
when(stream.state()).thenReturn(RESERVED_REMOTE); 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(stream).openForPush();
verify(observer).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), 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 @Test
public void headersReadForPromisedStreamShouldCloseStream() throws Exception { public void headersReadForPromisedStreamShouldCloseStream() throws Exception {
when(stream.state()).thenReturn(RESERVED_REMOTE); 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).openForPush();
verify(stream).close(); verify(stream).close();
verify(observer).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), 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 @Test
@ -444,14 +450,14 @@ public class DelegatingHttp2ConnectionHandlerTest {
@Test @Test
public void dataWriteAfterGoAwayShouldFail() throws Exception { public void dataWriteAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true); 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); assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
} }
@Test @Test
public void dataWriteShouldSucceed() throws Exception { public void dataWriteShouldSucceed() throws Exception {
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false); handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false);
verify(outboundFlow).sendFlowControlled(eq(STREAM_ID), eq(dummyData()), eq(0), eq(false), verify(outboundFlow).sendFlowControlled(eq(STREAM_ID), eq(dummyData()), eq(0),
eq(false), any(Http2OutboundFlowController.FrameWriter.class)); eq(false), any(Http2OutboundFlowController.FrameWriter.class));
} }
@ -459,49 +465,49 @@ public class DelegatingHttp2ConnectionHandlerTest {
public void headersWriteAfterGoAwayShouldFail() throws Exception { public void headersWriteAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true); when(connection.isGoAway()).thenReturn(true);
ChannelFuture future = handler.writeHeaders( 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(local, never()).createStream(anyInt(), anyBoolean());
verify(writer, never()).writeHeaders(eq(ctx), eq(promise), anyInt(), 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); assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
} }
@Test @Test
public void headersWriteForUnknownStreamShouldCreateStream() throws Exception { public void headersWriteForUnknownStreamShouldCreateStream() throws Exception {
when(local.createStream(eq(5), eq(false))).thenReturn(stream); 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(local).createStream(eq(5), eq(false));
verify(writer).writeHeaders(eq(ctx), eq(promise), eq(5), eq(EMPTY_HEADERS), eq(0), 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 @Test
public void headersWriteShouldCreateHalfClosedStream() throws Exception { public void headersWriteShouldCreateHalfClosedStream() throws Exception {
when(local.createStream(eq(5), eq(true))).thenReturn(stream); 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(local).createStream(eq(5), eq(true));
verify(writer).writeHeaders(eq(ctx), eq(promise), eq(5), eq(EMPTY_HEADERS), eq(0), 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 @Test
public void headersWriteShouldOpenStreamForPush() throws Exception { public void headersWriteShouldOpenStreamForPush() throws Exception {
when(stream.state()).thenReturn(RESERVED_LOCAL); 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).openForPush();
verify(stream, never()).closeLocalSide(); verify(stream, never()).closeLocalSide();
verify(writer).writeHeaders(eq(ctx), eq(promise), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), 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 @Test
public void headersWriteShouldClosePushStream() throws Exception { public void headersWriteShouldClosePushStream() throws Exception {
when(stream.state()).thenReturn(RESERVED_LOCAL).thenReturn(HALF_CLOSED_LOCAL); 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).openForPush();
verify(stream).closeLocalSide(); verify(stream).closeLocalSide();
verify(writer).writeHeaders(eq(ctx), eq(promise), eq(STREAM_ID), eq(EMPTY_HEADERS), eq(0), 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 @Test

View File

@ -14,6 +14,20 @@
*/ */
package io.netty.handler.codec.http2; 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.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -27,45 +41,21 @@ import io.netty.channel.ChannelPromise;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.FullHttpRequest;
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.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest; 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 io.netty.util.NetUtil;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; 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 * Testing the {@link DelegatingHttp2HttpConnectionHandler} for {@link FullHttpRequest} objects into HTTP/2 frames
*/ */
@ -77,22 +67,18 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
@Mock @Mock
private Http2FrameObserver serverObserver; private Http2FrameObserver serverObserver;
private Http2FrameWriter frameWriter;
private ServerBootstrap sb; private ServerBootstrap sb;
private Bootstrap cb; private Bootstrap cb;
private Channel serverChannel; private Channel serverChannel;
private Channel clientChannel; private Channel clientChannel;
private CountDownLatch requestLatch; private CountDownLatch requestLatch;
private long maxContentLength;
private static final int CONNECTION_SETUP_READ_COUNT = 2; private static final int CONNECTION_SETUP_READ_COUNT = 2;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
maxContentLength = 1 << 16;
requestLatch = new CountDownLatch(CONNECTION_SETUP_READ_COUNT + 1); requestLatch = new CountDownLatch(CONNECTION_SETUP_READ_COUNT + 1);
frameWriter = new DefaultHttp2FrameWriter();
sb = new ServerBootstrap(); sb = new ServerBootstrap();
cb = new Bootstrap(); cb = new Bootstrap();
@ -104,6 +90,7 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast(new DelegatingHttp2ConnectionHandler(true, new FrameCountDown())); p.addLast(new DelegatingHttp2ConnectionHandler(true, new FrameCountDown()));
p.addLast(ignoreSettingsHandler());
} }
}); });
@ -114,6 +101,7 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast(new DelegatingHttp2HttpConnectionHandler(false, clientObserver)); p.addLast(new DelegatingHttp2HttpConnectionHandler(false, clientObserver));
p.addLast(ignoreSettingsHandler());
} }
}); });
@ -155,9 +143,9 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
assertTrue(writeFuture.isSuccess()); assertTrue(writeFuture.isSuccess());
awaitRequests(); awaitRequests();
verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(5), eq(http2Headers), eq(0), 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), verify(serverObserver, never()).onDataRead(any(ChannelHandlerContext.class),
anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(), anyBoolean()); anyInt(), any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -174,8 +162,6 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder() final Http2Headers http2Headers = new DefaultHttp2Headers.Builder()
.method("POST").path("/example").authority("www.example.org:5555").scheme("http") .method("POST").path("/example").authority("www.example.org:5555").scheme("http")
.add("foo", "goo").add("foo", "goo2").add("foo2", "goo2").build(); .add("foo", "goo").add("foo", "goo2").add("foo2", "goo2").build();
final HttpContent expectedContent = new DefaultLastHttpContent(Unpooled.copiedBuffer(text.getBytes()),
true);
ChannelPromise writePromise = newPromise(); ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise); ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
@ -185,9 +171,9 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
assertTrue(writeFuture.isSuccess()); assertTrue(writeFuture.isSuccess());
awaitRequests(); awaitRequests();
verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2Headers), eq(0), 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), 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 { private void awaitRequests() throws Exception {
@ -210,26 +196,25 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment) boolean endOfStream)
throws Http2Exception { throws Http2Exception {
serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream, serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream);
endOfSegment);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, boolean endSegment) throws Http2Exception { int padding, boolean endStream) throws Http2Exception {
serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, 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, serverObserver.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream);
requestLatch.countDown(); requestLatch.countDown();
} }

View File

@ -76,6 +76,7 @@ public class Http2ConnectionRoundtripTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast(new DelegatingHttp2ConnectionHandler(true, new FrameCountDown())); 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 { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast(new DelegatingHttp2ConnectionHandler(false, clientObserver)); p.addLast(new DelegatingHttp2ConnectionHandler(false, clientObserver));
p.addLast(Http2CodecUtil.ignoreSettingsHandler());
} }
}); });
@ -117,23 +119,22 @@ public class Http2ConnectionRoundtripTest {
public void run() { public void run() {
for (int i = 0, nextStream = 3; i < NUM_STREAMS; ++i, nextStream += 2) { for (int i = 0, nextStream = 3; i < NUM_STREAMS; ++i, nextStream += 2) {
http2Client.writeHeaders( 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.writePing(ctx(), newPromise(), Unpooled.copiedBuffer(pingMsg.getBytes()));
http2Client.writeData( http2Client.writeData(
ctx(), newPromise(), nextStream, ctx(), newPromise(), nextStream,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true); Unpooled.copiedBuffer(text.getBytes()), 0, true);
} }
} }
}); });
// Wait for all frames to be received. // Wait for all frames to be received.
awaitRequests(); awaitRequests();
verify(serverObserver, times(NUM_STREAMS)).onHeadersRead(any(ChannelHandlerContext.class), verify(serverObserver, times(NUM_STREAMS)).onHeadersRead(any(ChannelHandlerContext.class),
anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false), anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false));
eq(false));
verify(serverObserver, times(NUM_STREAMS)).onPingRead(any(ChannelHandlerContext.class), verify(serverObserver, times(NUM_STREAMS)).onPingRead(any(ChannelHandlerContext.class),
eq(Unpooled.copiedBuffer(pingMsg.getBytes()))); eq(Unpooled.copiedBuffer(pingMsg.getBytes())));
verify(serverObserver, times(NUM_STREAMS)).onDataRead(any(ChannelHandlerContext.class), 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 { private void awaitRequests() throws Exception {
@ -156,26 +157,25 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment) boolean endOfStream)
throws Http2Exception { throws Http2Exception {
serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream, serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream);
endOfSegment);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, boolean endSegment) throws Http2Exception { int padding, boolean endStream) throws Http2Exception {
serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, 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, serverObserver.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream);
requestLatch.countDown(); 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.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.NetUtil; import io.netty.util.NetUtil;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -81,6 +82,7 @@ public class Http2FrameRoundtripTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast("reader", new FrameAdapter(serverObserver)); p.addLast("reader", new FrameAdapter(serverObserver));
p.addLast(Http2CodecUtil.ignoreSettingsHandler());
} }
}); });
@ -91,6 +93,7 @@ public class Http2FrameRoundtripTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast("reader", new FrameAdapter(null)); p.addLast("reader", new FrameAdapter(null));
p.addLast(Http2CodecUtil.ignoreSettingsHandler());
} }
}); });
@ -116,12 +119,12 @@ public class Http2FrameRoundtripTest {
@Override @Override
public void run() { public void run() {
frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF, frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF,
Unpooled.copiedBuffer(text.getBytes()), 100, true, false); Unpooled.copiedBuffer(text.getBytes()), 100, true);
} }
}); });
awaitRequests(); awaitRequests();
verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
dataCaptor.capture(), eq(100), eq(true), eq(false)); dataCaptor.capture(), eq(100), eq(true));
} }
@Test @Test
@ -132,12 +135,12 @@ public class Http2FrameRoundtripTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { public void run() {
frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 0, true, false); frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 0, true);
} }
}); });
awaitRequests(); awaitRequests();
verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(headers), eq(0), eq(true), eq(false)); eq(headers), eq(0), eq(true));
} }
@Test @Test
@ -149,12 +152,12 @@ public class Http2FrameRoundtripTest {
@Override @Override
public void run() { public void run() {
frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 4, (short) 255, frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 4, (short) 255,
true, 0, true, false); true, 0, true);
} }
}); });
awaitRequests(); awaitRequests();
verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), 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 @Test
@ -271,9 +274,9 @@ public class Http2FrameRoundtripTest {
public void run() { public void run() {
for (int i = 1; i < numStreams + 1; ++i) { for (int i = 1; i < numStreams + 1; ++i) {
frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false, frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false,
0, false, false); 0, false);
frameWriter.writeData(ctx(), newPromise(), i, 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 @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, boolean endOfSegment) int padding, boolean endOfStream)
throws Http2Exception { throws Http2Exception {
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream, observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream);
endOfSegment);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, boolean endSegment) Http2Headers headers, int padding, boolean endStream)
throws Http2Exception { throws Http2Exception {
observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); observer.onHeadersRead(ctx, streamId, headers, padding, endStream);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, Http2Headers headers, int streamDependency, short weight,
boolean exclusive, int padding, boolean endStream, boolean endSegment) boolean exclusive, int padding, boolean endStream)
throws Http2Exception { throws Http2Exception {
observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream);
requestLatch.countDown(); requestLatch.countDown();
} }

View File

@ -15,6 +15,7 @@
package io.netty.handler.codec.http2; 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.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -41,6 +42,8 @@ public class Http2SettingsTest {
assertNull(settings.initialWindowSize()); assertNull(settings.initialWindowSize());
assertNull(settings.maxConcurrentStreams()); assertNull(settings.maxConcurrentStreams());
assertNull(settings.pushEnabled()); assertNull(settings.pushEnabled());
assertNull(settings.maxFrameSize());
assertNull(settings.maxHeaderListSize());
} }
@Test @Test
@ -49,9 +52,13 @@ public class Http2SettingsTest {
settings.maxConcurrentStreams(2); settings.maxConcurrentStreams(2);
settings.pushEnabled(true); settings.pushEnabled(true);
settings.headerTableSize(3); settings.headerTableSize(3);
settings.maxFrameSize(MAX_FRAME_SIZE_UPPER_BOUND);
settings.maxHeaderListSize(4);
assertEquals(1, (int) settings.initialWindowSize()); assertEquals(1, (int) settings.initialWindowSize());
assertEquals(2L, (long) settings.maxConcurrentStreams()); assertEquals(2L, (long) settings.maxConcurrentStreams());
assertTrue(settings.pushEnabled()); assertTrue(settings.pushEnabled());
assertEquals(3L, (long) settings.headerTableSize()); 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; 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.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -24,30 +31,28 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; 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.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultHttpRequest; 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.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.HttpContent;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders; 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.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.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable; import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable;
import io.netty.util.NetUtil; 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.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -55,17 +60,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; 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 * Testing the {@link InboundHttp2ToHttpAdapter} for HTTP/2 frames into {@link HttpObject}s
*/ */
@ -151,7 +145,7 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { public void run() {
frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, true, false); frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -174,9 +168,9 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { 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, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true); Unpooled.copiedBuffer(text.getBytes()), 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -205,11 +199,11 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { 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, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, false, false); Unpooled.copiedBuffer(text.getBytes()), 0, false);
frameWriter.writeData(ctx(), newPromise(), 3, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text2.getBytes()), 0, true, true); Unpooled.copiedBuffer(text2.getBytes()), 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -238,13 +232,13 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { 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, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, false, false); Unpooled.copiedBuffer(text.getBytes()), 0, false);
frameWriter.writeData(ctx(), newPromise(), 3, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, false, false); Unpooled.copiedBuffer(text.getBytes()), 0, false);
frameWriter.writeData(ctx(), newPromise(), 3, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true); Unpooled.copiedBuffer(text.getBytes()), 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -278,10 +272,10 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { public void run() {
frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false);
frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, false, false); frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, false);
frameWriter.writeData(ctx(), newPromise(), 3, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true); Unpooled.copiedBuffer(text.getBytes()), 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -315,10 +309,10 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { 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, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, false, false); Unpooled.copiedBuffer(text.getBytes()), 0, false);
frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, true, true); frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers2, 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -359,12 +353,12 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { 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.writePushPromise(ctx(), newPromise(), 3, 5, http2Headers2, 0);
frameWriter.writeData(ctx(), newPromise(), 3, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true); Unpooled.copiedBuffer(text.getBytes()), 0, true);
frameWriter.writeData(ctx(), newPromise(), 5, frameWriter.writeData(ctx(), newPromise(), 5,
Unpooled.copiedBuffer(text2.getBytes()), 0, true, true); Unpooled.copiedBuffer(text2.getBytes()), 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -405,13 +399,13 @@ public class InboundHttp2ToHttpAdapterTest {
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { public void run() {
frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false, false); frameWriter.writeHeaders(ctx(), newPromise(), 3, http2Headers, 0, false);
frameWriter.writeHeaders(ctx(), newPromise(), 5, http2Headers2, 0, false, false); frameWriter.writeHeaders(ctx(), newPromise(), 5, http2Headers2, 0, false);
frameWriter.writePriority(ctx(), newPromise(), 5, 3, (short) 256, true); frameWriter.writePriority(ctx(), newPromise(), 5, 3, (short) 256, true);
frameWriter.writeData(ctx(), newPromise(), 3, frameWriter.writeData(ctx(), newPromise(), 3,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true); Unpooled.copiedBuffer(text.getBytes()), 0, true);
frameWriter.writeData(ctx(), newPromise(), 5, frameWriter.writeData(ctx(), newPromise(), 5,
Unpooled.copiedBuffer(text2.getBytes()), 0, true, true); Unpooled.copiedBuffer(text2.getBytes()), 0, true);
} }
}); });
awaitRequests(); awaitRequests();
@ -474,28 +468,27 @@ public class InboundHttp2ToHttpAdapterTest {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, boolean endOfSegment) int padding, boolean endOfStream)
throws Http2Exception { throws Http2Exception {
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream, observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream);
endOfSegment);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, boolean endSegment) Http2Headers headers, int padding, boolean endStream)
throws Http2Exception { throws Http2Exception {
observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); observer.onHeadersRead(ctx, streamId, headers, padding, endStream);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, Http2Headers headers, int streamDependency, short weight,
boolean exclusive, int padding, boolean endStream, boolean endSegment) boolean exclusive, int padding, boolean endStream)
throws Http2Exception { throws Http2Exception {
observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream);
requestLatch.countDown(); requestLatch.countDown();
} }

View File

@ -14,10 +14,12 @@
*/ */
package io.netty.example.http2.client; 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.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
@ -34,9 +36,6 @@ import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.TimeUnit; 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 * 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 * 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.handler.codec.http2.Http2HttpHeaders;
import io.netty.util.CharsetUtil; 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.Iterator;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
/** /**
* Process {@link FullHttpResponse} translated from HTTP/2 frames * Process {@link FullHttpResponse} translated from HTTP/2 frames

View File

@ -69,7 +69,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
Http2Headers headers = Http2Headers headers =
DefaultHttp2Headers.newBuilder().status("200") DefaultHttp2Headers.newBuilder().status("200")
.set(UPGRADE_RESPONSE_HEADER, "true").build(); .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); super.userEventTriggered(ctx, evt);
} }
@ -79,7 +79,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
*/ */
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment) throws Http2Exception { boolean endOfStream) throws Http2Exception {
if (endOfStream) { if (endOfStream) {
sendResponse(ctx(), streamId, data.retain()); sendResponse(ctx(), streamId, data.retain());
} }
@ -91,8 +91,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, Http2Headers headers, int streamDependency, short weight,
boolean exclusive, int padding, boolean endStream, boolean endSegment) boolean exclusive, int padding, boolean endStream) throws Http2Exception {
throws Http2Exception {
if (endStream) { if (endStream) {
sendResponse(ctx(), streamId, RESPONSE_BYTES.duplicate()); sendResponse(ctx(), streamId, RESPONSE_BYTES.duplicate());
} }
@ -110,8 +109,8 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) { private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) {
// Send a frame for the response status // Send a frame for the response status
Http2Headers headers = DefaultHttp2Headers.newBuilder().status("200").build(); 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);
} }
} }