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