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