Upgrade to HTTP/2 draft 13
Motivation: Need to upgrade HTTP/2 implementation to latest draft. Modifications: Various changes to support draft 13. Result: Support for HTTP/2 draft 13.
This commit is contained in:
parent
58cc16d106
commit
67159e7119
@ -43,7 +43,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twitter</groupId>
|
||||
<artifactId>hpack</artifactId>
|
||||
<version>0.7.0</version>
|
||||
<version>0.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
|
@ -15,6 +15,21 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.toByteBuf;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.toHttp2Exception;
|
||||
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_LOCAL;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
@ -27,11 +42,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
import static io.netty.handler.codec.http2.Http2Error.*;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.*;
|
||||
|
||||
/**
|
||||
* Abstract base class for a handler of HTTP/2 frames. Handles reading and writing of HTTP/2 frames
|
||||
* as well as management of connection state and flow control for both inbound and outbound data
|
||||
@ -63,11 +73,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
private ChannelFutureListener closeListener;
|
||||
|
||||
protected AbstractHttp2ConnectionHandler(boolean server) {
|
||||
this(server, false);
|
||||
}
|
||||
|
||||
protected AbstractHttp2ConnectionHandler(boolean server, boolean allowCompression) {
|
||||
this(new DefaultHttp2Connection(server, allowCompression));
|
||||
this(new DefaultHttp2Connection(server));
|
||||
}
|
||||
|
||||
protected AbstractHttp2ConnectionHandler(Http2Connection connection) {
|
||||
@ -201,10 +207,9 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
*/
|
||||
public final Http2Settings settings() {
|
||||
Http2Settings settings = new Http2Settings();
|
||||
settings.allowCompressedData(connection.local().allowCompressedData());
|
||||
settings.initialWindowSize(inboundFlow.initialInboundWindowSize());
|
||||
settings.maxConcurrentStreams(connection.remote().maxStreams());
|
||||
settings.maxHeaderTableSize(frameReader.maxHeaderTableSize());
|
||||
settings.headerTableSize(frameReader.maxHeaderTableSize());
|
||||
if (!connection.isServer()) {
|
||||
// Only set the pushEnabled flag if this is a client endpoint.
|
||||
settings.pushEnabled(connection.local().allowPushTo());
|
||||
@ -217,7 +222,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
*/
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
||||
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +232,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
*/
|
||||
@Override
|
||||
public final void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
|
||||
int padding, boolean endStream, boolean endSegment) {
|
||||
int padding, boolean endStream, boolean endSegment) throws Http2Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,15 +317,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
* Default implementation. Does nothing.
|
||||
*/
|
||||
@Override
|
||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation. Does nothing.
|
||||
*/
|
||||
@Override
|
||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
||||
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||
ByteBuf payload) {
|
||||
}
|
||||
|
||||
protected final ChannelHandlerContext ctx() {
|
||||
@ -346,10 +344,6 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
throw protocolError("Sending data after connection going away.");
|
||||
}
|
||||
|
||||
if (!connection.remote().allowCompressedData() && compressed) {
|
||||
throw protocolError("compression is disallowed for remote endpoint.");
|
||||
}
|
||||
|
||||
Http2Stream stream = connection.requireStream(streamId);
|
||||
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
|
||||
|
||||
@ -450,7 +444,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
throw protocolError("Sending settings after connection going away.");
|
||||
}
|
||||
|
||||
if (settings.hasPushEnabled() && connection.isServer()) {
|
||||
Boolean pushEnabled = settings.pushEnabled();
|
||||
if (pushEnabled != null && connection.isServer()) {
|
||||
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
|
||||
}
|
||||
|
||||
@ -493,15 +488,6 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
}
|
||||
}
|
||||
|
||||
protected ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
||||
if (!connection.isServer()) {
|
||||
return promise.setFailure(protocolError("Client sending ALT_SVC frame"));
|
||||
}
|
||||
return frameWriter.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host,
|
||||
origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
|
||||
throws Exception {
|
||||
@ -724,27 +710,28 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
* Applies settings received from the remote endpoint.
|
||||
*/
|
||||
private void applyRemoteSettings(Http2Settings settings) throws Http2Exception {
|
||||
if (settings.hasPushEnabled()) {
|
||||
Boolean pushEnabled = settings.pushEnabled();
|
||||
if (pushEnabled != null) {
|
||||
if (!connection.isServer()) {
|
||||
throw protocolError("Client received SETTINGS frame with ENABLE_PUSH specified");
|
||||
}
|
||||
connection.remote().allowPushTo(settings.pushEnabled());
|
||||
connection.remote().allowPushTo(pushEnabled);
|
||||
}
|
||||
|
||||
if (settings.hasAllowCompressedData()) {
|
||||
connection.remote().allowCompressedData(settings.allowCompressedData());
|
||||
Long maxConcurrentStreams = settings.maxConcurrentStreams();
|
||||
if (maxConcurrentStreams != null) {
|
||||
int value = (int) Math.min(maxConcurrentStreams, Integer.MAX_VALUE);
|
||||
connection.local().maxStreams(value);
|
||||
}
|
||||
|
||||
if (settings.hasMaxConcurrentStreams()) {
|
||||
connection.local().maxStreams(settings.maxConcurrentStreams());
|
||||
Long headerTableSize = settings.headerTableSize();
|
||||
if (headerTableSize != null) {
|
||||
frameWriter.maxHeaderTableSize(headerTableSize);
|
||||
}
|
||||
|
||||
if (settings.hasMaxHeaderTableSize()) {
|
||||
frameWriter.maxHeaderTableSize(settings.maxHeaderTableSize());
|
||||
}
|
||||
|
||||
if (settings.hasInitialWindowSize()) {
|
||||
outboundFlow.initialOutboundWindowSize(settings.initialWindowSize());
|
||||
Integer initialWindowSize = settings.initialWindowSize();
|
||||
if (initialWindowSize != null) {
|
||||
outboundFlow.initialOutboundWindowSize(initialWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
@ -769,23 +756,19 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
|
||||
@Override
|
||||
public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
||||
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||
verifyPrefaceReceived();
|
||||
|
||||
if (!connection.local().allowCompressedData() && compressed) {
|
||||
throw protocolError("compression is disallowed.");
|
||||
}
|
||||
|
||||
// Check if we received a data frame for a stream which is half-closed
|
||||
Http2Stream stream = connection.requireStream(streamId);
|
||||
stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
|
||||
|
||||
// Apply flow control.
|
||||
inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, endOfSegment,
|
||||
compressed, new Http2InboundFlowController.FrameWriter() {
|
||||
|
||||
new Http2InboundFlowController.FrameWriter() {
|
||||
@Override
|
||||
public void writeFrame(int streamId, int windowSizeIncrement) {
|
||||
public void writeFrame(int streamId, int windowSizeIncrement)
|
||||
throws Http2Exception {
|
||||
frameWriter.writeWindowUpdate(ctx, ctx.newPromise(), streamId,
|
||||
windowSizeIncrement);
|
||||
}
|
||||
@ -803,7 +786,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
}
|
||||
|
||||
AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream,
|
||||
endOfSegment, compressed);
|
||||
endOfSegment);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -941,27 +924,28 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
* Applies settings sent from the local endpoint.
|
||||
*/
|
||||
private void applyLocalSettings(Http2Settings settings) throws Http2Exception {
|
||||
if (settings.hasPushEnabled()) {
|
||||
Boolean pushEnabled = settings.pushEnabled();
|
||||
if (pushEnabled != null) {
|
||||
if (connection.isServer()) {
|
||||
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
|
||||
}
|
||||
connection.local().allowPushTo(settings.pushEnabled());
|
||||
connection.local().allowPushTo(pushEnabled);
|
||||
}
|
||||
|
||||
if (settings.hasAllowCompressedData()) {
|
||||
connection.local().allowCompressedData(settings.allowCompressedData());
|
||||
Long maxConcurrentStreams = settings.maxConcurrentStreams();
|
||||
if (maxConcurrentStreams != null) {
|
||||
int value = (int) Math.min(maxConcurrentStreams, Integer.MAX_VALUE);
|
||||
connection.remote().maxStreams(value);
|
||||
}
|
||||
|
||||
if (settings.hasMaxConcurrentStreams()) {
|
||||
connection.remote().maxStreams(settings.maxConcurrentStreams());
|
||||
Long headerTableSize = settings.headerTableSize();
|
||||
if (headerTableSize != null) {
|
||||
frameReader.maxHeaderTableSize(headerTableSize);
|
||||
}
|
||||
|
||||
if (settings.hasMaxHeaderTableSize()) {
|
||||
frameReader.maxHeaderTableSize(settings.maxHeaderTableSize());
|
||||
}
|
||||
|
||||
if (settings.hasInitialWindowSize()) {
|
||||
inboundFlow.initialInboundWindowSize(settings.initialWindowSize());
|
||||
Integer initialWindowSize = settings.initialWindowSize();
|
||||
if (initialWindowSize != null) {
|
||||
inboundFlow.initialInboundWindowSize(initialWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1045,31 +1029,9 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
||||
if (connection.isServer()) {
|
||||
throw protocolError("Server received ALT_SVC frame");
|
||||
}
|
||||
AbstractHttp2ConnectionHandler.this.onAltSvcRead(ctx, streamId, maxAge, port,
|
||||
protocolId, host, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
||||
verifyPrefaceReceived();
|
||||
|
||||
Http2Stream stream = connection.requireStream(streamId);
|
||||
verifyGoAwayNotReceived();
|
||||
verifyRstStreamNotReceived(stream);
|
||||
if (stream.state() == CLOSED || shouldIgnoreFrame(stream)) {
|
||||
// Ignored for closed streams.
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the outbound flow controller.
|
||||
outboundFlow.setBlocked(streamId);
|
||||
|
||||
AbstractHttp2ConnectionHandler.this.onBlockedRead(ctx, streamId);
|
||||
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||
ByteBuf payload) {
|
||||
AbstractHttp2ConnectionHandler.this.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1160,12 +1122,11 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
||||
|
||||
// Write the frame.
|
||||
ChannelFuture future =
|
||||
frameWriter.writeData(ctx, chunkPromise, streamId, data,
|
||||
padding, endStream, endSegment, compressed);
|
||||
frameWriter.writeData(ctx, chunkPromise, streamId, data, padding, endStream,
|
||||
endSegment);
|
||||
|
||||
// Close the connection on write failures that leave the outbound
|
||||
// flow
|
||||
// control window in a corrupt state.
|
||||
// flow control window in a corrupt state.
|
||||
future.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
|
@ -45,39 +45,28 @@ public class DefaultHttp2Connection implements Http2Connection {
|
||||
private final Http2StreamRemovalPolicy removalPolicy;
|
||||
|
||||
/**
|
||||
* Creates a connection with compression disabled and an immediate stream removal policy.
|
||||
* Creates a connection with an immediate stream removal policy.
|
||||
*
|
||||
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
||||
*/
|
||||
public DefaultHttp2Connection(boolean server) {
|
||||
this(server, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection with an immediate stream removal policy.
|
||||
*
|
||||
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
||||
* @param allowCompressedData if true, compressed frames are allowed from the remote end-point.
|
||||
*/
|
||||
public DefaultHttp2Connection(boolean server, boolean allowCompressedData) {
|
||||
this(server, allowCompressedData, immediateRemovalPolicy());
|
||||
this(server, immediateRemovalPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection with the given settings.
|
||||
*
|
||||
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
||||
* @param allowCompressedData if true, compressed frames are allowed from the remote end-point.
|
||||
* @param removalPolicy the policy to be used for removal of closed stream.
|
||||
*/
|
||||
public DefaultHttp2Connection(boolean server, boolean allowCompressedData,
|
||||
public DefaultHttp2Connection(boolean server,
|
||||
Http2StreamRemovalPolicy removalPolicy) {
|
||||
if (removalPolicy == null) {
|
||||
throw new NullPointerException("removalPolicy");
|
||||
}
|
||||
this.removalPolicy = removalPolicy;
|
||||
localEndpoint = new DefaultEndpoint(server, allowCompressedData);
|
||||
remoteEndpoint = new DefaultEndpoint(!server, false);
|
||||
localEndpoint = new DefaultEndpoint(server);
|
||||
remoteEndpoint = new DefaultEndpoint(!server);
|
||||
|
||||
// Tell the removal policy how to remove a stream from this connection.
|
||||
removalPolicy.setAction(new Action() {
|
||||
@ -600,8 +589,7 @@ public class DefaultHttp2Connection implements Http2Connection {
|
||||
private int nextStreamId;
|
||||
private int lastStreamCreated;
|
||||
private int lastKnownStream = -1;
|
||||
private boolean pushToAllowed;
|
||||
private boolean allowCompressedData;
|
||||
private boolean pushToAllowed = true;
|
||||
|
||||
/**
|
||||
* The maximum number of active streams allowed to be created by this endpoint.
|
||||
@ -613,8 +601,7 @@ public class DefaultHttp2Connection implements Http2Connection {
|
||||
*/
|
||||
private int numActiveStreams;
|
||||
|
||||
DefaultEndpoint(boolean server, boolean allowCompressedData) {
|
||||
this.allowCompressedData = allowCompressedData;
|
||||
DefaultEndpoint(boolean server) {
|
||||
this.server = server;
|
||||
|
||||
// Determine the starting stream ID for this endpoint. Client-initiated streams
|
||||
@ -738,16 +725,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
||||
this.maxStreams = maxStreams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowCompressedData() {
|
||||
return allowCompressedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allowCompressedData(boolean allow) {
|
||||
allowCompressedData = allow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastStreamCreated() {
|
||||
return lastStreamCreated;
|
||||
|
@ -15,14 +15,28 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_LENGTH_MASK;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.readUnsignedInt;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
||||
import static io.netty.util.CharsetUtil.*;
|
||||
|
||||
/**
|
||||
* A {@link Http2FrameReader} that supports all frame types defined by the HTTP/2 specification.
|
||||
*/
|
||||
@ -37,7 +51,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
private final Http2HeadersDecoder headersDecoder;
|
||||
|
||||
private State state = State.FRAME_HEADER;
|
||||
private Http2FrameType frameType;
|
||||
private byte frameType;
|
||||
private int streamId;
|
||||
private Http2Flags flags;
|
||||
private int payloadLength;
|
||||
@ -52,12 +66,12 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maxHeaderTableSize(int max) {
|
||||
headersDecoder.maxHeaderTableSize(max);
|
||||
public void maxHeaderTableSize(long max) {
|
||||
headersDecoder.maxHeaderTableSize((int) Math.min(max, Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxHeaderTableSize() {
|
||||
public long maxHeaderTableSize() {
|
||||
return headersDecoder.maxHeaderTableSize();
|
||||
}
|
||||
|
||||
@ -120,7 +134,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
|
||||
// Read the header and prepare the unmarshaller to read the frame.
|
||||
payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK;
|
||||
frameType = Http2FrameType.forTypeCode(in.readUnsignedByte());
|
||||
frameType = in.readByte();
|
||||
flags = new Http2Flags(in.readUnsignedByte());
|
||||
streamId = readUnsignedInt(in);
|
||||
|
||||
@ -155,22 +169,16 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
case CONTINUATION:
|
||||
verifyContinuationFrame();
|
||||
break;
|
||||
case ALT_SVC:
|
||||
verifyAltSvcFrame();
|
||||
break;
|
||||
case BLOCKED:
|
||||
verifyBlockedFrame();
|
||||
break;
|
||||
default:
|
||||
throw protocolError("Unsupported frame type: %s", frameType);
|
||||
// Unknown frame type, could be an extension.
|
||||
break;
|
||||
}
|
||||
|
||||
// Start reading the payload for the frame.
|
||||
state = State.FRAME_PAYLOAD;
|
||||
}
|
||||
|
||||
private void
|
||||
processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
|
||||
private void processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
|
||||
throws Http2Exception {
|
||||
if (in.readableBytes() < payloadLength) {
|
||||
// Wait until the entire payload has been read.
|
||||
@ -212,15 +220,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
case CONTINUATION:
|
||||
readContinuationFrame(payload, observer);
|
||||
break;
|
||||
case ALT_SVC:
|
||||
readAltSvcFrame(ctx, payload, observer);
|
||||
break;
|
||||
case BLOCKED:
|
||||
observer.onBlockedRead(ctx, streamId);
|
||||
break;
|
||||
default:
|
||||
// Should never happen.
|
||||
throw protocolError("Unsupported frame type: %s", frameType);
|
||||
readUnknownFrame(ctx, payload, observer);
|
||||
break;
|
||||
}
|
||||
|
||||
// Go back to reading the next frame header.
|
||||
@ -231,10 +233,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
verifyNotProcessingHeaders();
|
||||
verifyPayloadLength(payloadLength);
|
||||
|
||||
if (!flags.isPaddingLengthValid()) {
|
||||
throw protocolError("Pad high is set but pad low is not");
|
||||
}
|
||||
if (payloadLength < flags.getNumPaddingLengthBytes()) {
|
||||
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
|
||||
throw protocolError("Frame length %d too small.", payloadLength);
|
||||
}
|
||||
}
|
||||
@ -243,18 +242,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
verifyNotProcessingHeaders();
|
||||
verifyPayloadLength(payloadLength);
|
||||
|
||||
int lengthWithoutPriority = flags.getNumPaddingLengthBytes();
|
||||
if (lengthWithoutPriority < 0) {
|
||||
int requiredLength = flags.getPaddingPresenceFieldLength() + flags.getNumPriorityBytes();
|
||||
if (payloadLength < requiredLength) {
|
||||
throw protocolError("Frame length too small." + payloadLength);
|
||||
}
|
||||
|
||||
if (!flags.isPaddingLengthValid()) {
|
||||
throw protocolError("Pad high is set but pad low is not");
|
||||
}
|
||||
|
||||
if (lengthWithoutPriority < flags.getNumPaddingLengthBytes()) {
|
||||
throw protocolError("Frame length %d too small for padding.", payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyPriorityFrame() throws Http2Exception {
|
||||
@ -291,15 +282,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
verifyNotProcessingHeaders();
|
||||
verifyPayloadLength(payloadLength);
|
||||
|
||||
if (!flags.isPaddingLengthValid()) {
|
||||
throw protocolError("Pad high is set but pad low is not");
|
||||
}
|
||||
|
||||
// Subtract the length of the promised stream ID field, to determine the length of the
|
||||
// rest of the payload (header block fragment + payload).
|
||||
int lengthWithoutPromisedId = payloadLength - INT_FIELD_LENGTH;
|
||||
if (lengthWithoutPromisedId < flags.getNumPaddingLengthBytes()) {
|
||||
throw protocolError("Frame length %d too small for padding.", payloadLength);
|
||||
int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
|
||||
if (payloadLength < minLength) {
|
||||
throw protocolError("Frame length %d too small.", payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,48 +334,25 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
+ "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
|
||||
}
|
||||
|
||||
if (!flags.isPaddingLengthValid()) {
|
||||
throw protocolError("Pad high is set but pad low is not");
|
||||
}
|
||||
|
||||
if (payloadLength < flags.getNumPaddingLengthBytes()) {
|
||||
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
|
||||
throw protocolError("Frame length %d too small for padding.", payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyAltSvcFrame() throws Http2Exception {
|
||||
verifyNotProcessingHeaders();
|
||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
||||
verifyPayloadLength(payloadLength);
|
||||
|
||||
if (payloadLength < 8) {
|
||||
throw protocolError("Frame length too small." + payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyBlockedFrame() throws Http2Exception {
|
||||
verifyNotProcessingHeaders();
|
||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
||||
|
||||
if (payloadLength != 0) {
|
||||
throw protocolError("Invalid frame length %d.", payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
||||
Http2FrameObserver observer) throws Http2Exception {
|
||||
int dataPadding = flags.readPaddingLength(payload);
|
||||
short padding = readPadding(payload);
|
||||
|
||||
// Determine how much data there is to read by removing the trailing
|
||||
// padding.
|
||||
int dataLength = payload.readableBytes() - dataPadding;
|
||||
int dataLength = payload.readableBytes() - padding;
|
||||
if (dataLength < 0) {
|
||||
throw protocolError("Frame payload too small for padding.");
|
||||
}
|
||||
|
||||
ByteBuf data = payload.readSlice(dataLength);
|
||||
observer.onDataRead(ctx, streamId, data, dataPadding, flags.endOfStream(),
|
||||
flags.endOfSegment(), flags.compressed());
|
||||
observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream(),
|
||||
flags.endOfSegment());
|
||||
payload.skipBytes(payload.readableBytes());
|
||||
}
|
||||
|
||||
@ -396,7 +360,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
Http2FrameObserver observer) throws Http2Exception {
|
||||
final int headersStreamId = streamId;
|
||||
final Http2Flags headersFlags = flags;
|
||||
int padding = flags.readPaddingLength(payload);
|
||||
final int padding = readPadding(payload);
|
||||
|
||||
// The callback that is invoked is different depending on whether priority information
|
||||
// is present in the headers frame.
|
||||
@ -415,7 +379,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||
Http2FrameObserver observer) throws Http2Exception {
|
||||
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
||||
if (endOfHeaders) {
|
||||
@ -429,7 +393,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
};
|
||||
|
||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -442,7 +406,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||
Http2FrameObserver observer) throws Http2Exception {
|
||||
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
||||
if (endOfHeaders) {
|
||||
@ -456,7 +420,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
|
||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
|
||||
}
|
||||
|
||||
private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
||||
@ -480,45 +444,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
observer.onSettingsAckRead(ctx);
|
||||
} else {
|
||||
int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
|
||||
Http2Settings settings = new Http2Settings();
|
||||
Http2Settings settings = new Http2Settings(5);
|
||||
for (int index = 0; index < numSettings; ++index) {
|
||||
short id = payload.readUnsignedByte();
|
||||
int id = payload.readUnsignedShort();
|
||||
long value = payload.readUnsignedInt();
|
||||
switch (id) {
|
||||
case SETTINGS_HEADER_TABLE_SIZE:
|
||||
if (value < 0 || value > Integer.MAX_VALUE) {
|
||||
throw protocolError("Invalid value for HEADER_TABLE_SIZE: %d", value);
|
||||
}
|
||||
settings.maxHeaderTableSize((int) value);
|
||||
break;
|
||||
case SETTINGS_COMPRESS_DATA:
|
||||
if (value != 0 && value != 1) {
|
||||
throw protocolError("Invalid value for COMPRESS_DATA: %d", value);
|
||||
}
|
||||
settings.allowCompressedData(value == 1);
|
||||
break;
|
||||
case SETTINGS_ENABLE_PUSH:
|
||||
if (value != 0 && value != 1) {
|
||||
throw protocolError("Invalid value for ENABLE_PUSH: %d", value);
|
||||
}
|
||||
settings.pushEnabled(value == 1);
|
||||
break;
|
||||
case SETTINGS_INITIAL_WINDOW_SIZE:
|
||||
if (value < 0 || value > Integer.MAX_VALUE) {
|
||||
throw protocolError("Invalid value for INITIAL_WINDOW_SIZE: %d", value);
|
||||
}
|
||||
settings.initialWindowSize((int) value);
|
||||
break;
|
||||
case SETTINGS_MAX_CONCURRENT_STREAMS:
|
||||
if (value < 0 || value > Integer.MAX_VALUE) {
|
||||
throw protocolError("Invalid value for MAX_CONCURRENT_STREAMS: %d",
|
||||
value);
|
||||
}
|
||||
settings.maxConcurrentStreams((int) value);
|
||||
break;
|
||||
default:
|
||||
throw protocolError("Unsupport setting: %d", id);
|
||||
}
|
||||
settings.put(id, value);
|
||||
}
|
||||
observer.onSettingsRead(ctx, settings);
|
||||
}
|
||||
@ -527,7 +457,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
|
||||
Http2FrameObserver observer) throws Http2Exception {
|
||||
final int pushPromiseStreamId = streamId;
|
||||
int padding = flags.readPaddingLength(payload);
|
||||
final int padding = readPadding(payload);
|
||||
final int promisedStreamId = readUnsignedInt(payload);
|
||||
|
||||
// Create a handler that invokes the observer when the header block is complete.
|
||||
@ -538,7 +468,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||
Http2FrameObserver observer) throws Http2Exception {
|
||||
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
||||
if (endOfHeaders) {
|
||||
@ -552,7 +482,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
|
||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
|
||||
}
|
||||
|
||||
private void readPingFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
||||
@ -581,30 +511,26 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
|
||||
private void readContinuationFrame(ByteBuf payload, Http2FrameObserver observer)
|
||||
throws Http2Exception {
|
||||
int padding = flags.readPaddingLength(payload);
|
||||
|
||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes() - padding);
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment, padding,
|
||||
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes());
|
||||
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment,
|
||||
observer);
|
||||
}
|
||||
|
||||
private void readAltSvcFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
||||
Http2FrameObserver observer) throws Http2Exception {
|
||||
long maxAge = payload.readUnsignedInt();
|
||||
int port = payload.readUnsignedShort();
|
||||
payload.skipBytes(1);
|
||||
short protocolIdLength = payload.readUnsignedByte();
|
||||
ByteBuf protocolId = payload.readSlice(protocolIdLength);
|
||||
short hostLength = payload.readUnsignedByte();
|
||||
String host = payload.toString(payload.readerIndex(), hostLength, UTF_8);
|
||||
payload.skipBytes(hostLength);
|
||||
String origin = null;
|
||||
if (payload.isReadable()) {
|
||||
origin = payload.toString(UTF_8);
|
||||
payload.skipBytes(payload.readableBytes());
|
||||
private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload, Http2FrameObserver observer)
|
||||
throws Http2Exception {
|
||||
payload = payload.readSlice(payload.readableBytes());
|
||||
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* If padding is present in the payload, reads the next byte as padding. Otherwise, returns zero.
|
||||
*/
|
||||
private short readPadding(ByteBuf payload) {
|
||||
if (!flags.paddingPresent()) {
|
||||
return 0;
|
||||
}
|
||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
|
||||
return payload.readUnsignedByte();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -625,11 +551,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
||||
*
|
||||
* @param endOfHeaders whether the fragment is the last in the header block.
|
||||
* @param fragment the fragment of the header block to be added.
|
||||
* @param padding the amount of padding to be supplied to the {@link Http2FrameObserver}
|
||||
* callback.
|
||||
* @param observer the observer to be notified if the header block is completed.
|
||||
*/
|
||||
abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
||||
abstract void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||
Http2FrameObserver observer) throws Http2Exception;
|
||||
|
||||
final HeadersBuilder builder() {
|
||||
|
@ -15,14 +15,34 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeader;
|
||||
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.Http2FrameTypes.CONTINUATION;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
import static io.netty.util.CharsetUtil.*;
|
||||
import io.netty.util.collection.IntObjectMap;
|
||||
|
||||
/**
|
||||
* A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification.
|
||||
@ -40,12 +60,12 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maxHeaderTableSize(int max) throws Http2Exception {
|
||||
headersEncoder.maxHeaderTableSize(max);
|
||||
public void maxHeaderTableSize(long max) throws Http2Exception {
|
||||
headersEncoder.maxHeaderTableSize((int) Math.min(max, Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxHeaderTableSize() {
|
||||
public long maxHeaderTableSize() {
|
||||
return headersEncoder.maxHeaderTableSize();
|
||||
}
|
||||
|
||||
@ -56,21 +76,21 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
||||
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed) {
|
||||
ByteBuf data, int padding, boolean endStream, boolean endSegment) {
|
||||
try {
|
||||
verifyStreamId(streamId, "Stream ID");
|
||||
verifyPadding(padding);
|
||||
|
||||
Http2Flags flags =
|
||||
Http2Flags.newBuilder().setPaddingFlags(padding).endOfStream(endStream)
|
||||
.endOfSegment(endSegment).compressed(compressed).build();
|
||||
new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream)
|
||||
.endOfSegment(endSegment);
|
||||
|
||||
int payloadLength = data.readableBytes() + padding + flags.getNumPaddingLengthBytes();
|
||||
int payloadLength = data.readableBytes() + padding + flags.getPaddingPresenceFieldLength();
|
||||
verifyPayloadLength(payloadLength);
|
||||
|
||||
ByteBuf out = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||
|
||||
writeFrameHeader(out, payloadLength, Http2FrameType.DATA, flags, streamId);
|
||||
writeFrameHeader(out, payloadLength, DATA, flags, streamId);
|
||||
|
||||
writePaddingLength(padding, out);
|
||||
|
||||
@ -111,9 +131,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
verifyWeight(weight);
|
||||
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH);
|
||||
writeFrameHeader(frame, PRIORITY_ENTRY_LENGTH, Http2FrameType.PRIORITY,
|
||||
Http2Flags.EMPTY, streamId);
|
||||
long word1 = exclusive ? 0x80000000L | streamDependency : streamDependency;
|
||||
writeFrameHeader(frame, PRIORITY_ENTRY_LENGTH, PRIORITY,
|
||||
new Http2Flags(), streamId);
|
||||
long word1 = exclusive ? (0x80000000L | streamDependency) : streamDependency;
|
||||
writeUnsignedInt(word1, frame);
|
||||
|
||||
// Adjust the weight so that it fits into a single byte on the wire.
|
||||
@ -132,7 +152,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
verifyErrorCode(errorCode);
|
||||
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH);
|
||||
writeFrameHeader(frame, INT_FIELD_LENGTH, Http2FrameType.RST_STREAM, Http2Flags.EMPTY,
|
||||
writeFrameHeader(frame, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(),
|
||||
streamId);
|
||||
writeUnsignedInt(errorCode, frame);
|
||||
return ctx.writeAndFlush(frame, promise);
|
||||
@ -145,10 +165,16 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
public ChannelFuture writeSettings(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
Http2Settings settings) {
|
||||
try {
|
||||
int payloadLength = calcSettingsPayloadLength(settings);
|
||||
if (settings == null) {
|
||||
throw new NullPointerException("settings");
|
||||
}
|
||||
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||
writeFrameHeader(frame, payloadLength, Http2FrameType.SETTINGS, Http2Flags.EMPTY, 0);
|
||||
writeSettingsPayload(settings, frame);
|
||||
writeFrameHeader(frame, payloadLength, SETTINGS, new Http2Flags(), 0);
|
||||
for (IntObjectMap.Entry<Long> entry : settings.entries()) {
|
||||
writeUnsignedShort(entry.key(), frame);
|
||||
writeUnsignedInt(entry.value(), frame);
|
||||
}
|
||||
return ctx.writeAndFlush(frame, promise);
|
||||
} catch (RuntimeException e) {
|
||||
return promise.setFailure(e);
|
||||
@ -159,7 +185,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
|
||||
try {
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
||||
writeFrameHeader(frame, 0, Http2FrameType.SETTINGS, Http2Flags.ACK_ONLY, 0);
|
||||
writeFrameHeader(frame, 0, SETTINGS, new Http2Flags().ack(true), 0);
|
||||
return ctx.writeAndFlush(frame, promise);
|
||||
} catch (RuntimeException e) {
|
||||
return promise.setFailure(e);
|
||||
@ -171,8 +197,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
ByteBuf data) {
|
||||
try {
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + data.readableBytes());
|
||||
Http2Flags flags = ack ? Http2Flags.ACK_ONLY : Http2Flags.EMPTY;
|
||||
writeFrameHeader(frame, data.readableBytes(), Http2FrameType.PING, flags, 0);
|
||||
Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
|
||||
writeFrameHeader(frame, data.readableBytes(), PING, flags, 0);
|
||||
|
||||
// Write the debug data.
|
||||
frame.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||
@ -198,21 +224,18 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
headersEncoder.encodeHeaders(headers, headerBlock);
|
||||
|
||||
// Read the first fragment (possibly everything).
|
||||
Http2Flags.Builder flags = Http2Flags.newBuilder().setPaddingFlags(padding);
|
||||
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
|
||||
int promisedStreamIdLength = INT_FIELD_LENGTH;
|
||||
int maxFragmentLength =
|
||||
MAX_FRAME_PAYLOAD_LENGTH
|
||||
- (promisedStreamIdLength + padding + flags.getNumPaddingLengthBytes());
|
||||
int nonFragmentLength = promisedStreamIdLength + padding + flags.getPaddingPresenceFieldLength();
|
||||
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength;
|
||||
ByteBuf fragment =
|
||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||
|
||||
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||
flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||
|
||||
int payloadLength =
|
||||
fragment.readableBytes() + promisedStreamIdLength + padding
|
||||
+ flags.getNumPaddingLengthBytes();
|
||||
int payloadLength = fragment.readableBytes() + nonFragmentLength;
|
||||
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||
writeFrameHeader(firstFrame, payloadLength, Http2FrameType.PUSH_PROMISE, flags.build(),
|
||||
writeFrameHeader(firstFrame, payloadLength, PUSH_PROMISE, flags,
|
||||
streamId);
|
||||
|
||||
writePaddingLength(padding, firstFrame);
|
||||
@ -250,7 +273,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
|
||||
int payloadLength = 8 + debugData.readableBytes();
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||
writeFrameHeader(frame, payloadLength, Http2FrameType.GO_AWAY, Http2Flags.EMPTY, 0);
|
||||
writeFrameHeader(frame, payloadLength, GO_AWAY, new Http2Flags(), 0);
|
||||
frame.writeInt(lastStreamId);
|
||||
writeUnsignedInt(errorCode, frame);
|
||||
frame.writeBytes(debugData, debugData.readerIndex(), debugData.readableBytes());
|
||||
@ -270,8 +293,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
verifyWindowSizeIncrement(windowSizeIncrement);
|
||||
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH);
|
||||
writeFrameHeader(frame, INT_FIELD_LENGTH, Http2FrameType.WINDOW_UPDATE,
|
||||
Http2Flags.EMPTY, streamId);
|
||||
writeFrameHeader(frame, INT_FIELD_LENGTH, WINDOW_UPDATE,
|
||||
new Http2Flags(), streamId);
|
||||
frame.writeInt(windowSizeIncrement);
|
||||
return ctx.writeAndFlush(frame, promise);
|
||||
} catch (RuntimeException e) {
|
||||
@ -280,49 +303,13 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
||||
public ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
|
||||
try {
|
||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
||||
verifyMaxAge(maxAge);
|
||||
verifyPort(port);
|
||||
|
||||
// 9 bytes is the total of all fields except for the protocol ID and host.
|
||||
// Breakdown: Max-Age(4) + Port(2) + reserved(1) + Proto-Len(1) + Host-Len(1) = 9
|
||||
int payloadLength = 9 + protocolId.readableBytes() + host.length();
|
||||
if (origin != null) {
|
||||
payloadLength += origin.length();
|
||||
}
|
||||
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||
writeFrameHeader(frame, payloadLength, Http2FrameType.ALT_SVC, Http2Flags.EMPTY,
|
||||
streamId);
|
||||
writeUnsignedInt(maxAge, frame);
|
||||
writeUnsignedShort(port, frame);
|
||||
frame.writeZero(1);
|
||||
frame.writeByte(protocolId.readableBytes());
|
||||
frame.writeBytes(protocolId);
|
||||
frame.writeByte(host.length());
|
||||
frame.writeBytes(host.getBytes(UTF_8));
|
||||
if (origin != null) {
|
||||
frame.writeBytes(origin.getBytes(UTF_8));
|
||||
}
|
||||
return ctx.writeAndFlush(frame, promise);
|
||||
} catch (RuntimeException e) {
|
||||
return promise.setFailure(e);
|
||||
} finally {
|
||||
protocolId.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
int streamId) {
|
||||
try {
|
||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
||||
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
||||
writeFrameHeader(frame, 0, Http2FrameType.BLOCKED, Http2Flags.EMPTY, streamId);
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
|
||||
writeFrameHeader(frame, payload.readableBytes(), frameType, flags, streamId);
|
||||
frame.writeBytes(payload);
|
||||
return ctx.writeAndFlush(frame, promise);
|
||||
} catch (RuntimeException e) {
|
||||
return promise.setFailure(e);
|
||||
@ -345,23 +332,23 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
headerBlock = ctx.alloc().buffer();
|
||||
headersEncoder.encodeHeaders(headers, headerBlock);
|
||||
|
||||
Http2Flags.Builder flags =
|
||||
Http2Flags.newBuilder().endOfStream(endStream).endOfSegment(endSegment)
|
||||
.priorityPresent(hasPriority).setPaddingFlags(padding);
|
||||
Http2Flags flags =
|
||||
new Http2Flags().endOfStream(endStream).endOfSegment(endSegment)
|
||||
.priorityPresent(hasPriority).paddingPresent(padding > 0);
|
||||
|
||||
// Read the first fragment (possibly everything).
|
||||
int nonFragmentBytes =
|
||||
padding + flags.getNumPriorityBytes() + flags.getNumPaddingLengthBytes();
|
||||
padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength();
|
||||
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentBytes;
|
||||
ByteBuf fragment =
|
||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||
|
||||
// Set the end of headers flag for the first frame.
|
||||
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||
flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||
|
||||
int payloadLength = fragment.readableBytes() + nonFragmentBytes;
|
||||
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||
writeFrameHeader(firstFrame, payloadLength, Http2FrameType.HEADERS, flags.build(),
|
||||
writeFrameHeader(firstFrame, payloadLength, HEADERS, flags,
|
||||
streamId);
|
||||
|
||||
// Write the padding length.
|
||||
@ -425,17 +412,17 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
*/
|
||||
private static ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId,
|
||||
ByteBuf headerBlock, int padding) {
|
||||
Http2Flags.Builder flags = Http2Flags.newBuilder().setPaddingFlags(padding);
|
||||
int maxFragmentLength =
|
||||
MAX_FRAME_PAYLOAD_LENGTH - (padding + flags.getNumPaddingLengthBytes());
|
||||
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
|
||||
int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength();
|
||||
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength;
|
||||
ByteBuf fragment =
|
||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||
|
||||
int payloadLength = fragment.readableBytes() + padding + flags.getNumPaddingLengthBytes();
|
||||
int payloadLength = fragment.readableBytes() + nonFragmentLength;
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||
|
||||
writeFrameHeader(frame, payloadLength, Http2FrameType.CONTINUATION, flags.build(), streamId);
|
||||
writeFrameHeader(frame, payloadLength, CONTINUATION, flags, streamId);
|
||||
|
||||
writePaddingLength(padding, frame);
|
||||
|
||||
@ -474,7 +461,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
}
|
||||
|
||||
private static void verifyPadding(int padding) {
|
||||
if (padding < 0 || padding > MAX_UNSIGNED_SHORT) {
|
||||
if (padding < 0 || padding > MAX_UNSIGNED_BYTE) {
|
||||
throw new IllegalArgumentException("Invalid padding value: " + padding);
|
||||
}
|
||||
}
|
||||
@ -503,16 +490,4 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
||||
throw new IllegalArgumentException("WindowSizeIncrement must be >= 0");
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyMaxAge(long maxAge) {
|
||||
if (maxAge < 0 || maxAge > MAX_UNSIGNED_INT) {
|
||||
throw new IllegalArgumentException("Invalid Max Age: " + maxAge);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyPort(int port) {
|
||||
if (port < 0 || port > MAX_UNSIGNED_SHORT) {
|
||||
throw new IllegalArgumentException("Invalid port: " + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.flowControlError;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@ -27,7 +27,7 @@ import io.netty.buffer.ByteBuf;
|
||||
public class DefaultHttp2InboundFlowController implements Http2InboundFlowController {
|
||||
|
||||
private final Http2Connection connection;
|
||||
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||
private int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
|
||||
public DefaultHttp2InboundFlowController(Http2Connection connection) {
|
||||
if (connection == null) {
|
||||
@ -66,7 +66,7 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro
|
||||
|
||||
@Override
|
||||
public void applyInboundFlowControl(int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed, FrameWriter frameWriter)
|
||||
boolean endOfStream, boolean endOfSegment, FrameWriter frameWriter)
|
||||
throws Http2Exception {
|
||||
int dataLength = data.readableBytes();
|
||||
applyConnectionFlowControl(dataLength, frameWriter);
|
||||
|
@ -57,7 +57,7 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
|
||||
};
|
||||
|
||||
private final Http2Connection connection;
|
||||
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||
private int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
|
||||
public DefaultHttp2OutboundFlowController(Http2Connection connection) {
|
||||
if (connection == null) {
|
||||
@ -147,11 +147,6 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlocked(int streamId) throws Http2Exception {
|
||||
// Ignore blocked frames. Just rely on window updates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream,
|
||||
boolean endSegment, boolean compressed, FrameWriter frameWriter) throws Http2Exception {
|
||||
|
@ -38,12 +38,6 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
public DelegatingHttp2ConnectionHandler(boolean server, boolean allowCompression,
|
||||
Http2FrameObserver observer) {
|
||||
super(server, allowCompression);
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
public DelegatingHttp2ConnectionHandler(Http2Connection connection,
|
||||
Http2FrameReader frameReader, Http2FrameWriter frameWriter,
|
||||
Http2InboundFlowController inboundFlow, Http2OutboundFlowController outboundFlow,
|
||||
@ -107,16 +101,10 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
|
||||
return super.writePushPromise(ctx, promise, streamId, promisedStreamId, headers, padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
||||
return super.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
||||
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment, compressed);
|
||||
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -178,13 +166,8 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
||||
observer.onBlockedRead(ctx, streamId);
|
||||
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||
ByteBuf payload) {
|
||||
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.util.collection.IntObjectMap;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -103,8 +104,12 @@ public class Http2ClientUpgradeCodec implements HttpClientUpgradeHandler.Upgrade
|
||||
Http2Settings settings = connectionHandler.settings();
|
||||
|
||||
// Serialize the payload of the SETTINGS frame.
|
||||
buf = ctx.alloc().buffer(calcSettingsPayloadLength(settings));
|
||||
writeSettingsPayload(settings, buf);
|
||||
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
|
||||
buf = ctx.alloc().buffer(payloadLength);
|
||||
for (IntObjectMap.Entry<Long> entry : settings.entries()) {
|
||||
writeUnsignedShort(entry.key(), buf);
|
||||
writeUnsignedInt(entry.value(), buf);
|
||||
}
|
||||
|
||||
// Base64 encode the payload and then convert to a string for the header.
|
||||
encodedBuf = Base64.encode(buf, URL_SAFE);
|
||||
|
@ -15,16 +15,15 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.format;
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2Error.*;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
||||
import static io.netty.util.CharsetUtil.*;
|
||||
|
||||
/**
|
||||
* Constants and utility method used for encoding/decoding HTTP2 frames.
|
||||
*/
|
||||
@ -36,7 +35,7 @@ public final class Http2CodecUtil {
|
||||
public static final int CONNECTION_STREAM_ID = 0;
|
||||
public static final int HTTP_UPGRADE_STREAM_ID = 1;
|
||||
public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings";
|
||||
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-12";
|
||||
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-13";
|
||||
|
||||
public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383;
|
||||
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
|
||||
@ -45,19 +44,19 @@ public final class Http2CodecUtil {
|
||||
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
|
||||
public static final int FRAME_HEADER_LENGTH = 8;
|
||||
public static final int FRAME_LENGTH_MASK = 0x3FFF;
|
||||
public static final int SETTING_ENTRY_LENGTH = 5;
|
||||
public static final int SETTING_ENTRY_LENGTH = 6;
|
||||
public static final int PRIORITY_ENTRY_LENGTH = 5;
|
||||
public static final int INT_FIELD_LENGTH = 4;
|
||||
public static final short MAX_WEIGHT = 256;
|
||||
public static final short MIN_WEIGHT = 1;
|
||||
public static final short MAX_WEIGHT = (short) 256;
|
||||
public static final short MIN_WEIGHT = (short) 1;
|
||||
|
||||
public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
|
||||
public static final short SETTINGS_ENABLE_PUSH = 2;
|
||||
public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
||||
public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
||||
public static final short SETTINGS_COMPRESS_DATA = 5;
|
||||
public static final int SETTINGS_HEADER_TABLE_SIZE = 1;
|
||||
public static final int SETTINGS_ENABLE_PUSH = 2;
|
||||
public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
||||
public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
||||
|
||||
public static final int DEFAULT_FLOW_CONTROL_WINDOW_SIZE = 65535;
|
||||
public static final int DEFAULT_WINDOW_SIZE = 65535;
|
||||
public static final boolean DEFAULT_ENABLE_PUSH = true;
|
||||
public static final short DEFAULT_PRIORITY_WEIGHT = 16;
|
||||
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
|
||||
public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
|
||||
@ -163,58 +162,15 @@ public final class Http2CodecUtil {
|
||||
/**
|
||||
* Writes an HTTP/2 frame header to the output buffer.
|
||||
*/
|
||||
public static void writeFrameHeader(ByteBuf out, int payloadLength, Http2FrameType type,
|
||||
public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
|
||||
Http2Flags flags, int streamId) {
|
||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||
out.writeShort(payloadLength);
|
||||
out.writeByte(type.typeCode());
|
||||
out.writeByte(type);
|
||||
out.writeByte(flags.value());
|
||||
out.writeInt(streamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the HTTP/2 SETTINGS payload length for the serialized representation
|
||||
* of the given settings.
|
||||
*/
|
||||
public static int calcSettingsPayloadLength(Http2Settings settings) {
|
||||
int numFields = 0;
|
||||
numFields += settings.hasAllowCompressedData() ? 1 : 0;
|
||||
numFields += settings.hasMaxHeaderTableSize() ? 1 : 0;
|
||||
numFields += settings.hasInitialWindowSize() ? 1 : 0;
|
||||
numFields += settings.hasMaxConcurrentStreams() ? 1 : 0;
|
||||
numFields += settings.hasPushEnabled() ? 1 : 0;
|
||||
|
||||
return SETTING_ENTRY_LENGTH * numFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the settings to the output buffer in the format of an HTTP/2 SETTINGS frame
|
||||
* payload.
|
||||
*/
|
||||
public static void writeSettingsPayload(Http2Settings settings, ByteBuf out) {
|
||||
if (settings.hasAllowCompressedData()) {
|
||||
out.writeByte(SETTINGS_COMPRESS_DATA);
|
||||
writeUnsignedInt(settings.allowCompressedData() ? 1L : 0L, out);
|
||||
}
|
||||
if (settings.hasMaxHeaderTableSize()) {
|
||||
out.writeByte(SETTINGS_HEADER_TABLE_SIZE);
|
||||
writeUnsignedInt(settings.maxHeaderTableSize(), out);
|
||||
}
|
||||
if (settings.hasInitialWindowSize()) {
|
||||
out.writeByte(SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
writeUnsignedInt(settings.initialWindowSize(), out);
|
||||
}
|
||||
if (settings.hasMaxConcurrentStreams()) {
|
||||
out.writeByte(SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
writeUnsignedInt(settings.maxConcurrentStreams(), out);
|
||||
}
|
||||
if (settings.hasPushEnabled()) {
|
||||
// Only write the enable push flag from client endpoints.
|
||||
out.writeByte(SETTINGS_ENABLE_PUSH);
|
||||
writeUnsignedInt(settings.pushEnabled() ? 1L : 0L, out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails the given promise with the cause and then re-throws the cause.
|
||||
*/
|
||||
|
@ -177,16 +177,6 @@ public interface Http2Connection {
|
||||
*/
|
||||
void maxStreams(int maxStreams);
|
||||
|
||||
/**
|
||||
* Indicates whether or not this endpoint allows compression.
|
||||
*/
|
||||
boolean allowCompressedData();
|
||||
|
||||
/**
|
||||
* Sets whether or not this endpoint allows compression.
|
||||
*/
|
||||
void allowCompressedData(boolean allow);
|
||||
|
||||
/**
|
||||
* Gets the ID of the stream last successfully created by this endpoint.
|
||||
*/
|
||||
|
@ -19,19 +19,19 @@ package io.netty.handler.codec.http2;
|
||||
* All error codes identified by the HTTP/2 spec.
|
||||
*/
|
||||
public enum Http2Error {
|
||||
NO_ERROR(0),
|
||||
PROTOCOL_ERROR(1),
|
||||
INTERNAL_ERROR(2),
|
||||
FLOW_CONTROL_ERROR(3),
|
||||
SETTINGS_TIMEOUT(4),
|
||||
STREAM_CLOSED(5),
|
||||
FRAME_SIZE_ERROR(6),
|
||||
REFUSED_STREAM(7),
|
||||
CANCEL(8),
|
||||
COMPRESSION_ERROR(9),
|
||||
CONNECT_ERROR(10),
|
||||
ENHANCE_YOUR_CALM(11),
|
||||
INADEQUATE_SECURITY(12);
|
||||
NO_ERROR(0x0),
|
||||
PROTOCOL_ERROR(0x1),
|
||||
INTERNAL_ERROR(0x2),
|
||||
FLOW_CONTROL_ERROR(0x3),
|
||||
SETTINGS_TIMEOUT(0x4),
|
||||
STREAM_CLOSED(0x5),
|
||||
FRAME_SIZE_ERROR(0x6),
|
||||
REFUSED_STREAM(0x7),
|
||||
CANCEL(0x8),
|
||||
COMPRESSION_ERROR(0x9),
|
||||
CONNECT_ERROR(0xA),
|
||||
ENHANCE_YOUR_CALM(0xB),
|
||||
INADEQUATE_SECURITY(0xC);
|
||||
|
||||
private final int code;
|
||||
|
||||
|
@ -15,28 +15,20 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Provides utility methods for accessing specific flags as defined by the HTTP/2 spec.
|
||||
*/
|
||||
public class Http2Flags {
|
||||
public static final Http2Flags EMPTY = new Http2Flags();
|
||||
public static final Http2Flags ACK_ONLY = new Builder().ack(true).build();
|
||||
|
||||
public final class Http2Flags {
|
||||
public static final short END_STREAM = 0x1;
|
||||
public static final short END_SEGMENT = 0x2;
|
||||
public static final short END_HEADERS = 0x4;
|
||||
public static final short ACK = 0x1;
|
||||
public static final short PAD_LOW = 0x8;
|
||||
public static final short PAD_HIGH = 0x10;
|
||||
public static final short PADDED = 0x8;
|
||||
public static final short PRIORITY = 0x20;
|
||||
public static final short COMPRESSED = 0x20;
|
||||
|
||||
private final short value;
|
||||
private short value;
|
||||
|
||||
private Http2Flags() {
|
||||
this((short) 0);
|
||||
public Http2Flags() {
|
||||
}
|
||||
|
||||
public Http2Flags(short value) {
|
||||
@ -55,7 +47,7 @@ public class Http2Flags {
|
||||
* frames.
|
||||
*/
|
||||
public boolean endOfStream() {
|
||||
return endOfStream(value);
|
||||
return isFlagSet(END_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,7 +55,7 @@ public class Http2Flags {
|
||||
* frames.
|
||||
*/
|
||||
public boolean endOfSegment() {
|
||||
return endOfSegment(value);
|
||||
return isFlagSet(END_SEGMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +63,7 @@ public class Http2Flags {
|
||||
* PUSH_PROMISE, and CONTINUATION frames.
|
||||
*/
|
||||
public boolean endOfHeaders() {
|
||||
return endOfHeaders(value);
|
||||
return isFlagSet(END_HEADERS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,7 +71,7 @@ public class Http2Flags {
|
||||
* dependency, and weight fields in a HEADERS frame.
|
||||
*/
|
||||
public boolean priorityPresent() {
|
||||
return priorityPresent(value);
|
||||
return isFlagSet(PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,46 +79,15 @@ public class Http2Flags {
|
||||
* SETTINGS and PING frames.
|
||||
*/
|
||||
public boolean ack() {
|
||||
return ack(value);
|
||||
return isFlagSet(ACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* For DATA frames, indicates that the data is compressed using gzip compression.
|
||||
*/
|
||||
public boolean compressed() {
|
||||
return compressed(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
|
||||
* For frames that include padding, indicates if the {@link #PADDED} field is present. Only
|
||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
||||
*/
|
||||
public boolean padLowPresent() {
|
||||
return padLowPresent(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
|
||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
||||
*/
|
||||
public boolean padHighPresent() {
|
||||
return padHighPresent(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
|
||||
* be set.
|
||||
*/
|
||||
public boolean isPaddingLengthValid() {
|
||||
return isPaddingLengthValid(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes expected in the padding length field of the payload. This is
|
||||
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
|
||||
*/
|
||||
public int getNumPaddingLengthBytes() {
|
||||
return getNumPaddingLengthBytes(value);
|
||||
public boolean paddingPresent() {
|
||||
return isFlagSet(PADDED);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,14 +95,81 @@ public class Http2Flags {
|
||||
* by the {@link #priorityPresent()} flag.
|
||||
*/
|
||||
public int getNumPriorityBytes() {
|
||||
return getNumPriorityBytes(value);
|
||||
return priorityPresent() ? 5 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the variable-length padding length field from the payload.
|
||||
* Gets the length in bytes of the padding presence field expected in the payload. This is
|
||||
* determined by the {@link #paddingPresent()} flag.
|
||||
*/
|
||||
public int readPaddingLength(ByteBuf payload) {
|
||||
return readPaddingLength(value, payload);
|
||||
public int getPaddingPresenceFieldLength() {
|
||||
return paddingPresent() ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #END_STREAM} flag.
|
||||
*/
|
||||
public Http2Flags endOfStream(boolean endOfStream) {
|
||||
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.
|
||||
*/
|
||||
public Http2Flags endOfHeaders(boolean endOfHeaders) {
|
||||
return setFlag(endOfHeaders, END_HEADERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #PRIORITY} flag.
|
||||
*/
|
||||
public Http2Flags priorityPresent(boolean priorityPresent) {
|
||||
return setFlag(priorityPresent, PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #PADDED} flag.
|
||||
*/
|
||||
public Http2Flags paddingPresent(boolean paddingPresent) {
|
||||
return setFlag(paddingPresent, PADDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #ACK} flag.
|
||||
*/
|
||||
public Http2Flags ack(boolean ack) {
|
||||
return setFlag(ack, ACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method to set any flag.
|
||||
* @param on if the flag should be enabled or disabled.
|
||||
* @param mask the mask that identifies the bit for the flag.
|
||||
* @return this instance.
|
||||
*/
|
||||
public Http2Flags setFlag(boolean on, short mask) {
|
||||
if (on) {
|
||||
value |= mask;
|
||||
} else {
|
||||
value &= ~mask;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not a particular flag is set.
|
||||
* @param mask the mask identifying the bit for the particular flag being tested
|
||||
* @return {@code true} if the flag is set
|
||||
*/
|
||||
public boolean isFlagSet(short mask) {
|
||||
return (value & mask) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -186,298 +214,10 @@ public class Http2Flags {
|
||||
if (endOfSegment()) {
|
||||
builder.append("END_OF_SEGMENT,");
|
||||
}
|
||||
if (padHighPresent()) {
|
||||
builder.append("PAD_HIGH,");
|
||||
}
|
||||
if (padLowPresent()) {
|
||||
builder.append("PAD_LOW,");
|
||||
if (paddingPresent()) {
|
||||
builder.append("PADDING_PRESENT,");
|
||||
}
|
||||
builder.append(')');
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for creating a new {@link Builder} instance.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for instances of {@link Http2Flags}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private short value;
|
||||
|
||||
/**
|
||||
* Sets the {@link #END_STREAM} flag.
|
||||
*/
|
||||
public Builder endOfStream(boolean endOfStream) {
|
||||
return setFlag(endOfStream, END_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #END_SEGMENT} flag.
|
||||
*/
|
||||
public Builder endOfSegment(boolean endOfSegment) {
|
||||
return setFlag(endOfSegment, END_SEGMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #END_HEADERS} flag.
|
||||
*/
|
||||
public Builder endOfHeaders(boolean endOfHeaders) {
|
||||
return setFlag(endOfHeaders, END_HEADERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #PRIORITY} flag.
|
||||
*/
|
||||
public Builder priorityPresent(boolean priorityPresent) {
|
||||
return setFlag(priorityPresent, PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #ACK} flag.
|
||||
*/
|
||||
public Builder ack(boolean ack) {
|
||||
return setFlag(ack, ACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #COMPRESSED} flag.
|
||||
*/
|
||||
public Builder compressed(boolean compressed) {
|
||||
return setFlag(compressed, COMPRESSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the padding flags in the given flags value as appropriate based on the padding
|
||||
* length.
|
||||
*/
|
||||
public Builder setPaddingFlags(int paddingLength) {
|
||||
if (paddingLength > 255) {
|
||||
value |= PAD_HIGH;
|
||||
}
|
||||
if (paddingLength > 0) {
|
||||
value |= PAD_LOW;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link #END_STREAM} flag is set. Only applies to DATA and HEADERS
|
||||
* frames.
|
||||
*/
|
||||
public boolean endOfStream() {
|
||||
return Http2Flags.endOfStream(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link #END_SEGMENT} flag is set. Only applies to DATA and HEADERS
|
||||
* frames.
|
||||
*/
|
||||
public boolean endOfSegment() {
|
||||
return Http2Flags.endOfSegment(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
|
||||
* PUSH_PROMISE, and CONTINUATION frames.
|
||||
*/
|
||||
public boolean endOfHeaders() {
|
||||
return Http2Flags.endOfHeaders(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the flag is set indicating the presence of the exclusive, stream
|
||||
* dependency, and weight fields in a HEADERS frame.
|
||||
*/
|
||||
public boolean priorityPresent() {
|
||||
return Http2Flags.priorityPresent(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the flag is set indicating that this frame is an ACK. Only applies for
|
||||
* SETTINGS and PING frames.
|
||||
*/
|
||||
public boolean ack() {
|
||||
return Http2Flags.ack(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For DATA frames, indicates that the data is compressed using gzip compression.
|
||||
*/
|
||||
public boolean compressed() {
|
||||
return Http2Flags.compressed(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
|
||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
||||
*/
|
||||
public boolean padLowPresent() {
|
||||
return Http2Flags.padLowPresent(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
|
||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
||||
*/
|
||||
public boolean padHighPresent() {
|
||||
return Http2Flags.padHighPresent(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
|
||||
* be set.
|
||||
*/
|
||||
public boolean isPaddingLengthValid() {
|
||||
return Http2Flags.isPaddingLengthValid(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes expected in the padding length field of the payload. This is
|
||||
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
|
||||
*/
|
||||
public int getNumPaddingLengthBytes() {
|
||||
return Http2Flags.getNumPaddingLengthBytes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes expected for the priority fields of the payload. This is determined
|
||||
* by the {@link #priorityPresent()} flag.
|
||||
*/
|
||||
public int getNumPriorityBytes() {
|
||||
return Http2Flags.getNumPriorityBytes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the variable-length padding length field from the payload.
|
||||
*/
|
||||
public int readPaddingLength(ByteBuf payload) {
|
||||
return Http2Flags.readPaddingLength(value, payload);
|
||||
}
|
||||
/**
|
||||
* Builds a new {@link Http2Flags} instance.
|
||||
*/
|
||||
public Http2Flags build() {
|
||||
return new Http2Flags(value);
|
||||
}
|
||||
|
||||
private Builder setFlag(boolean on, short mask) {
|
||||
if (on) {
|
||||
value |= mask;
|
||||
} else {
|
||||
value &= ~mask;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link #END_STREAM} flag is set. Only applies to DATA and HEADERS
|
||||
* frames.
|
||||
*/
|
||||
public static boolean endOfStream(short value) {
|
||||
return isFlagSet(value, END_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link #END_SEGMENT} flag is set. Only applies to DATA and HEADERS
|
||||
* frames.
|
||||
*/
|
||||
public static boolean endOfSegment(short value) {
|
||||
return isFlagSet(value, END_SEGMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
|
||||
* PUSH_PROMISE, and CONTINUATION frames.
|
||||
*/
|
||||
public static boolean endOfHeaders(short value) {
|
||||
return isFlagSet(value, END_HEADERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the flag is set indicating the presence of the exclusive, stream
|
||||
* dependency, and weight fields in a HEADERS frame.
|
||||
*/
|
||||
public static boolean priorityPresent(short value) {
|
||||
return isFlagSet(value, PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the flag is set indicating that this frame is an ACK. Only applies for
|
||||
* SETTINGS and PING frames.
|
||||
*/
|
||||
public static boolean ack(short value) {
|
||||
return isFlagSet(value, ACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* For DATA frames, indicates that the data is compressed using gzip compression.
|
||||
*/
|
||||
public static boolean compressed(short value) {
|
||||
return isFlagSet(value, COMPRESSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
|
||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
||||
*/
|
||||
public static boolean padLowPresent(short value) {
|
||||
return isFlagSet(value, PAD_LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
|
||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
||||
*/
|
||||
public static boolean padHighPresent(short value) {
|
||||
return isFlagSet(value, PAD_HIGH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
|
||||
* be set.
|
||||
*/
|
||||
public static boolean isPaddingLengthValid(short value) {
|
||||
return padHighPresent(value) ? padLowPresent(value) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes expected in the padding length field of the payload. This is
|
||||
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
|
||||
*/
|
||||
public static int getNumPaddingLengthBytes(short value) {
|
||||
return (padHighPresent(value) ? 1 : 0) + (padLowPresent(value) ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes expected for the priority fields of the payload. This is determined
|
||||
* by the {@link #priorityPresent()} flag.
|
||||
*/
|
||||
public static int getNumPriorityBytes(short value) {
|
||||
return priorityPresent(value) ? 5 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the variable-length padding length field from the payload.
|
||||
*/
|
||||
public static int readPaddingLength(short value, ByteBuf payload) {
|
||||
int paddingLength = 0;
|
||||
if (padHighPresent(value)) {
|
||||
paddingLength += payload.readUnsignedByte() * 256;
|
||||
}
|
||||
if (padLowPresent(value)) {
|
||||
paddingLength += payload.readUnsignedByte();
|
||||
}
|
||||
|
||||
return paddingLength;
|
||||
}
|
||||
|
||||
private static boolean isFlagSet(short value, short mask) {
|
||||
return (value & mask) != 0;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public class Http2FrameAdapter implements Http2FrameObserver {
|
||||
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
||||
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -81,11 +81,7 @@ public class Http2FrameAdapter implements Http2FrameObserver {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
||||
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||
ByteBuf payload) {
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.util.internal.logging.InternalLogLevel;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
@ -50,10 +51,10 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
||||
}
|
||||
|
||||
public void logData(Direction direction, int streamId, ByteBuf data, int padding,
|
||||
boolean endStream, boolean endSegment, boolean compressed) {
|
||||
boolean endStream, boolean endSegment) {
|
||||
log(direction,
|
||||
"DATA: streamId=%d, dataLen=%d, padding=%d, endStream=%b, endSegment=%b, compressed=%b",
|
||||
streamId, data.readableBytes(), padding, endStream, endSegment, compressed);
|
||||
"DATA: streamId=%d, padding=%d, endStream=%b, endSegment=%b, length=%d, bytes=%s",
|
||||
streamId, padding, endStream, endSegment, data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||
}
|
||||
|
||||
public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding,
|
||||
@ -90,11 +91,11 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
||||
}
|
||||
|
||||
public void logPing(Direction direction, ByteBuf data) {
|
||||
log(direction, "PING: ack=false, dataLen=%d", data.readableBytes());
|
||||
log(direction, "PING: ack=false, length=%d, bytes=%s", data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||
}
|
||||
|
||||
public void logPingAck(Direction direction, ByteBuf data) {
|
||||
log(direction, "PING: ack=true, dataLen=%d", data.readableBytes());
|
||||
log(direction, "PING: ack=true, length=%d, bytes=%s", data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||
}
|
||||
|
||||
public void logPushPromise(Direction direction, int streamId, int promisedStreamId,
|
||||
@ -104,8 +105,8 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
||||
}
|
||||
|
||||
public void logGoAway(Direction direction, int lastStreamId, long errorCode, ByteBuf debugData) {
|
||||
log(direction, "GO_AWAY: lastStreamId=%d, errorCode=%d, dataLen=%d", lastStreamId,
|
||||
errorCode, debugData.readableBytes());
|
||||
log(direction, "GO_AWAY: lastStreamId=%d, errorCode=%d, length=%d, bytes=%s", lastStreamId,
|
||||
errorCode, debugData.readableBytes(), ByteBufUtil.hexDump(debugData));
|
||||
}
|
||||
|
||||
public void logWindowsUpdate(Direction direction, int streamId, int windowSizeIncrement) {
|
||||
@ -113,15 +114,9 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
||||
windowSizeIncrement);
|
||||
}
|
||||
|
||||
public void logAltSvc(Direction direction, int streamId, long maxAge, int port,
|
||||
ByteBuf protocolId, String host, String origin) {
|
||||
log(direction,
|
||||
"ALT_SVC: streamId=%d, maxAge=%d, port=%d, protocolIdLen=%d, host=%s, origin=%s",
|
||||
streamId, maxAge, port, protocolId.readableBytes(), host, origin);
|
||||
}
|
||||
|
||||
public void logBlocked(Direction direction, int streamId) {
|
||||
log(direction, "BLOCKED: streamId=%d", streamId);
|
||||
public void logUnknownFrame(Direction direction, byte frameType, int streamId, Http2Flags flags, ByteBuf data) {
|
||||
log(direction, "UNKNOWN: frameType=%d, streamId=%d, flags=%d, length=%d, bytes=%s",
|
||||
frameType & 0xFF, streamId, flags.value(), data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||
}
|
||||
|
||||
private void log(Direction direction, String format, Object... args) {
|
||||
|
@ -34,10 +34,9 @@ public interface Http2FrameObserver {
|
||||
* @param endOfStream Indicates whether this is the last frame to be sent from the remote
|
||||
* endpoint for this stream.
|
||||
* @param endOfSegment Indicates whether this frame is the end of the current segment.
|
||||
* @param compressed Indicates whether or not the payload is compressed with gzip encoding.
|
||||
*/
|
||||
void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception;
|
||||
boolean endOfStream, boolean endOfSegment) throws Http2Exception;
|
||||
|
||||
/**
|
||||
* Handles an inbound HEADERS frame.
|
||||
@ -162,26 +161,13 @@ public interface Http2FrameObserver {
|
||||
throws Http2Exception;
|
||||
|
||||
/**
|
||||
* Handles an inbound ALT_SVC frame.
|
||||
* Handler for a frame not defined by the HTTP/2 spec.
|
||||
*
|
||||
* @param ctx the context from the handler where the frame was read.
|
||||
* @param streamId the stream.
|
||||
* @param maxAge the freshness lifetime of the alternative service association.
|
||||
* @param port the port that the alternative service is available upon.
|
||||
* @param protocolId the ALPN protocol identifier of the alternative service. If this buffer
|
||||
* needs to be retained by the observer they must make a copy.
|
||||
* @param host the host that the alternative service is available upon.
|
||||
* @param origin an optional origin that the alternative service is available upon. May be
|
||||
* {@code null}.
|
||||
* @param frameType the frame type from the HTTP/2 header.
|
||||
* @param streamId the stream the frame was sent on.
|
||||
* @param flags the flags in the frame header.
|
||||
* @param payload the payload of the frame.
|
||||
*/
|
||||
void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
||||
ByteBuf protocolId, String host, String origin) throws Http2Exception;
|
||||
|
||||
/**
|
||||
* Handles an inbound BLOCKED frame.
|
||||
*
|
||||
* @param ctx the context from the handler where the frame was read.
|
||||
* @param streamId the stream that is blocked or 0 if the entire connection is blocked.
|
||||
*/
|
||||
void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception;
|
||||
void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload);
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ public interface Http2FrameReader extends Closeable {
|
||||
/**
|
||||
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||
*/
|
||||
void maxHeaderTableSize(int max);
|
||||
void maxHeaderTableSize(long max);
|
||||
|
||||
/**
|
||||
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||
*/
|
||||
int maxHeaderTableSize();
|
||||
long maxHeaderTableSize();
|
||||
|
||||
/**
|
||||
* Closes this reader and frees any allocated resources.
|
||||
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Enumeration of all frame types defined by the HTTP/2 specification.
|
||||
*/
|
||||
public enum Http2FrameType {
|
||||
DATA((short) 0x0),
|
||||
HEADERS((short) 0x1),
|
||||
PRIORITY((short) 0x2),
|
||||
RST_STREAM((short) 0x3),
|
||||
SETTINGS((short) 0x4),
|
||||
PUSH_PROMISE((short) 0x5),
|
||||
PING((short) 0x6),
|
||||
GO_AWAY((short) 0x7),
|
||||
WINDOW_UPDATE((short) 0x8),
|
||||
CONTINUATION((short) 0x9),
|
||||
ALT_SVC((short) 0xA),
|
||||
BLOCKED((short) 0xB);
|
||||
|
||||
/**
|
||||
* Create an array indexed by the frame type code for fast lookup of the enum value.
|
||||
*/
|
||||
private static final Http2FrameType[] codeToTypeMap;
|
||||
static {
|
||||
int maxIndex = 0;
|
||||
for (Http2FrameType type : Http2FrameType.values()) {
|
||||
maxIndex = Math.max(maxIndex, type.typeCode());
|
||||
}
|
||||
codeToTypeMap = new Http2FrameType[maxIndex + 1];
|
||||
for (Http2FrameType type : Http2FrameType.values()) {
|
||||
codeToTypeMap[type.typeCode()] = type;
|
||||
}
|
||||
}
|
||||
|
||||
private final short code;
|
||||
|
||||
Http2FrameType(short code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the code used to represent this frame type on the wire.
|
||||
*/
|
||||
public short typeCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the frame type by it's type code.
|
||||
*/
|
||||
public static Http2FrameType forTypeCode(short typeCode) {
|
||||
Http2FrameType type = null;
|
||||
if (typeCode >= 0 && typeCode < codeToTypeMap.length) {
|
||||
type = codeToTypeMap[typeCode];
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Unsupported typeCode: " + typeCode);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Registry of all standard frame types defined by the HTTP/2 specification.
|
||||
*/
|
||||
public final class Http2FrameTypes {
|
||||
public static final byte DATA = 0x0;
|
||||
public static final byte HEADERS = 0x1;
|
||||
public static final byte PRIORITY = 0x2;
|
||||
public static final byte RST_STREAM = 0x3;
|
||||
public static final byte SETTINGS = 0x4;
|
||||
public static final byte PUSH_PROMISE = 0x5;
|
||||
public static final byte PING = 0x6;
|
||||
public static final byte GO_AWAY = 0x7;
|
||||
public static final byte WINDOW_UPDATE = 0x8;
|
||||
public static final byte CONTINUATION = 0x9;
|
||||
|
||||
private Http2FrameTypes() {
|
||||
}
|
||||
}
|
@ -37,11 +37,10 @@ public interface Http2FrameWriter extends Closeable {
|
||||
* @param padding the amount of padding to be added to the end of the frame
|
||||
* @param endStream indicates if this is the last frame to be sent for the stream.
|
||||
* @param endSegment indicates if this is the last frame in the current segment.
|
||||
* @param compressed indicates whether the data is compressed using gzip encoding.
|
||||
* @return the future for the write.
|
||||
*/
|
||||
ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
||||
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed);
|
||||
ByteBuf data, int padding, boolean endStream, boolean endSegment);
|
||||
|
||||
/**
|
||||
* Writes a HEADERS frame to the remote endpoint.
|
||||
@ -179,31 +178,18 @@ public interface Http2FrameWriter extends Closeable {
|
||||
int streamId, int windowSizeIncrement);
|
||||
|
||||
/**
|
||||
* Writes a ALT_SVC frame to the remote endpoint.
|
||||
* Generic write method for any HTTP/2 frame. This allows writing of non-standard frames.
|
||||
*
|
||||
* @param ctx the context to use for writing.
|
||||
* @param promise the promise for the write.
|
||||
* @param streamId the stream.
|
||||
* @param maxAge the freshness lifetime of the alternative service association.
|
||||
* @param port the port that the alternative service is available upon.
|
||||
* @param protocolId the ALPN protocol identifier of the alternative service.
|
||||
* @param host the host that the alternative service is available upon.
|
||||
* @param origin an optional origin that the alternative service is available upon. May be
|
||||
* {@code null}.
|
||||
* @param frameType the frame type identifier.
|
||||
* @param streamId the stream for which to send the frame.
|
||||
* @param flags the flags to write for this frame.
|
||||
* @param payload the payload to write for this frame.
|
||||
* @return the future for the write.
|
||||
*/
|
||||
ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
||||
long maxAge, int port, ByteBuf protocolId, String host, String origin);
|
||||
|
||||
/**
|
||||
* Writes a BLOCKED frame to the remote endpoint.
|
||||
*
|
||||
* @param ctx the context to use for writing.
|
||||
* @param promise the promise for the write.
|
||||
* @param streamId the stream that is blocked or 0 if the entire connection is blocked.
|
||||
* @return the future for the write.
|
||||
*/
|
||||
ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise, int streamId);
|
||||
ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise, byte frameType,
|
||||
int streamId, Http2Flags flags, ByteBuf payload);
|
||||
|
||||
/**
|
||||
* Closes this writer and frees any allocated resources.
|
||||
@ -214,10 +200,10 @@ public interface Http2FrameWriter extends Closeable {
|
||||
/**
|
||||
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||
*/
|
||||
void maxHeaderTableSize(int max) throws Http2Exception;
|
||||
void maxHeaderTableSize(long max) throws Http2Exception;
|
||||
|
||||
/**
|
||||
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||
*/
|
||||
int maxHeaderTableSize();
|
||||
long maxHeaderTableSize();
|
||||
}
|
||||
|
@ -59,6 +59,5 @@ public interface Http2InboundFlowController {
|
||||
* @throws Http2Exception thrown if any protocol-related error occurred.
|
||||
*/
|
||||
void applyInboundFlowControl(int streamId, ByteBuf data, int padding, boolean endOfStream,
|
||||
boolean endOfSegment, boolean compressed, FrameWriter frameWriter)
|
||||
throws Http2Exception;
|
||||
boolean endOfSegment, FrameWriter frameWriter) throws Http2Exception;
|
||||
}
|
||||
|
@ -46,11 +46,10 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
|
||||
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
|
||||
int padding, boolean endOfStream, boolean endOfSegment, boolean compressed)
|
||||
int padding, boolean endOfStream, boolean endOfSegment)
|
||||
throws Http2Exception {
|
||||
logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment,
|
||||
compressed);
|
||||
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment, compressed);
|
||||
logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment);
|
||||
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -132,16 +131,10 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge,
|
||||
int port, ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
||||
logger.logAltSvc(INBOUND, streamId, maxAge, port, protocolId, host, origin);
|
||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
||||
logger.logBlocked(INBOUND, streamId);
|
||||
observer.onBlockedRead(ctx, streamId);
|
||||
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
|
||||
Http2Flags flags, ByteBuf payload) {
|
||||
logger.logUnknownFrame(INBOUND, frameType, streamId, flags, payload);
|
||||
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -152,12 +145,12 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maxHeaderTableSize(int max) {
|
||||
public void maxHeaderTableSize(long max) {
|
||||
reader.maxHeaderTableSize(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxHeaderTableSize() {
|
||||
public long maxHeaderTableSize() {
|
||||
return reader.maxHeaderTableSize();
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
|
||||
|
||||
public enum SelectedProtocol {
|
||||
/** Must be updated to match the HTTP/2 draft number. */
|
||||
HTTP_2("h2-12"),
|
||||
HTTP_2("h2-13"),
|
||||
HTTP_1_1("http/1.1"),
|
||||
HTTP_1_0("http/1.0"),
|
||||
UNKNOWN("Unknown");
|
||||
|
@ -64,15 +64,6 @@ public interface Http2OutboundFlowController {
|
||||
*/
|
||||
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
|
||||
|
||||
/**
|
||||
* Indicates that the given stream or the entire connection is blocked and that no more messages
|
||||
* should be sent.
|
||||
*
|
||||
* @param streamId the stream ID that is blocked or zero if the entire connection is blocked.
|
||||
* @throws Http2Exception thrown if a protocol-related error occurred.
|
||||
*/
|
||||
void setBlocked(int streamId) throws Http2Exception;
|
||||
|
||||
/**
|
||||
* Sends the frame with outbound flow control applied. The frame may be written at a later time,
|
||||
* depending on whether the remote endpoint can receive the frame now.
|
||||
|
@ -43,10 +43,9 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
||||
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed) {
|
||||
logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment, compressed);
|
||||
return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment,
|
||||
compressed);
|
||||
ByteBuf data, int padding, boolean endStream, boolean endSegment) {
|
||||
logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment);
|
||||
return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,17 +120,10 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
||||
logger.logAltSvc(OUTBOUND, streamId, maxAge, port, protocolId, host, origin);
|
||||
return writer.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
int streamId) {
|
||||
logger.logBlocked(OUTBOUND, streamId);
|
||||
return writer.writeBlocked(ctx, promise, streamId);
|
||||
public ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||
byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
|
||||
logger.logUnknownFrame(OUTBOUND, frameType, streamId, flags, payload);
|
||||
return writer.writeFrame(ctx, promise, frameType, streamId, flags, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -140,12 +132,12 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maxHeaderTableSize(int max) throws Http2Exception {
|
||||
public void maxHeaderTableSize(long max) throws Http2Exception {
|
||||
writer.maxHeaderTableSize(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxHeaderTableSize() {
|
||||
public long maxHeaderTableSize() {
|
||||
return writer.maxHeaderTableSize();
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,13 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeader;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@ -27,12 +34,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.handler.codec.base64.Base64Dialect.*;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
import static io.netty.handler.codec.http2.Http2Flags.*;
|
||||
import static io.netty.handler.codec.http2.Http2FrameType.*;
|
||||
|
||||
/**
|
||||
* Server-side codec for performing a cleartext upgrade from HTTP/1.x to HTTP/2.
|
||||
*/
|
||||
@ -138,8 +139,9 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
|
||||
final Http2Settings decodedSettings = new Http2Settings();
|
||||
frameReader.readFrame(ctx, frame, new Http2FrameAdapter() {
|
||||
@Override
|
||||
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
|
||||
decodedSettings.copy(settings);
|
||||
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)
|
||||
throws Http2Exception {
|
||||
decodedSettings.copyFrom(settings);
|
||||
}
|
||||
});
|
||||
return decodedSettings;
|
||||
@ -153,7 +155,7 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
|
||||
*/
|
||||
private static ByteBuf createSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload) {
|
||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
|
||||
writeFrameHeader(frame, payload.readableBytes(), SETTINGS, EMPTY, 0);
|
||||
writeFrameHeader(frame, payload.readableBytes(), SETTINGS, new Http2Flags(), 0);
|
||||
frame.writeBytes(payload);
|
||||
payload.release();
|
||||
return frame;
|
||||
|
@ -15,258 +15,112 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_ENABLE_PUSH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
import io.netty.util.collection.IntObjectHashMap;
|
||||
|
||||
/**
|
||||
* Settings for one endpoint in an HTTP/2 connection. Each of the values are optional as defined in
|
||||
* the spec for the SETTINGS frame.
|
||||
* the spec for the SETTINGS frame. Permits storage of arbitrary key/value pairs but provides helper
|
||||
* methods for standard settings.
|
||||
*/
|
||||
public class Http2Settings {
|
||||
private static final byte MAX_HEADER_TABLE_SIZE_MASK = 0x1;
|
||||
private static final byte PUSH_ENABLED_MASK = 0x2;
|
||||
private static final byte MAX_CONCURRENT_STREAMS_MASK = 0x4;
|
||||
private static final byte INITIAL_WINDOW_SIZE_MASK = 0x8;
|
||||
private static final byte ALLOW_COMPRESSION_MASK = 0x10;
|
||||
public final class Http2Settings extends IntObjectHashMap<Long> {
|
||||
|
||||
private byte enabled;
|
||||
private int maxHeaderTableSize;
|
||||
private boolean pushEnabled;
|
||||
private int maxConcurrentStreams;
|
||||
private int initialWindowSize;
|
||||
private boolean allowCompressedData;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the headerTableSize value is available.
|
||||
*/
|
||||
public boolean hasMaxHeaderTableSize() {
|
||||
return isEnabled(MAX_HEADER_TABLE_SIZE_MASK);
|
||||
public Http2Settings() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum HPACK header table size or throws {@link NoSuchElementException} if the
|
||||
* value has not been set.
|
||||
*/
|
||||
public int maxHeaderTableSize() {
|
||||
if (!hasMaxHeaderTableSize()) {
|
||||
throw new NoSuchElementException("headerTableSize");
|
||||
}
|
||||
return maxHeaderTableSize;
|
||||
public Http2Settings(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum HPACK header table size to the specified value.
|
||||
*/
|
||||
public Http2Settings maxHeaderTableSize(int headerTableSize) {
|
||||
if (headerTableSize < 0) {
|
||||
throw new IllegalArgumentException("headerTableSize must be >= 0");
|
||||
}
|
||||
|
||||
enable(MAX_HEADER_TABLE_SIZE_MASK);
|
||||
maxHeaderTableSize = headerTableSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not the pushEnabled value is available.
|
||||
*/
|
||||
public boolean hasPushEnabled() {
|
||||
return isEnabled(PUSH_ENABLED_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether or not server push is enabled or throws {@link NoSuchElementException} if the
|
||||
* value has not been set.
|
||||
*/
|
||||
public boolean pushEnabled() {
|
||||
if (!hasPushEnabled()) {
|
||||
throw new NoSuchElementException("pushEnabled");
|
||||
}
|
||||
return pushEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not server push is enabled.
|
||||
*/
|
||||
public Http2Settings pushEnabled(boolean pushEnabled) {
|
||||
enable(PUSH_ENABLED_MASK);
|
||||
this.pushEnabled = pushEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not the maxConcurrentStreams value is available.
|
||||
*/
|
||||
public boolean hasMaxConcurrentStreams() {
|
||||
return isEnabled(MAX_CONCURRENT_STREAMS_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum allowed concurrent streams or throws {@link NoSuchElementException} if the
|
||||
* value has not been set.
|
||||
*/
|
||||
public int maxConcurrentStreams() {
|
||||
if (!hasMaxConcurrentStreams()) {
|
||||
throw new NoSuchElementException("maxConcurrentStreams");
|
||||
}
|
||||
return maxConcurrentStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum allowed concurrent streams to the specified value.
|
||||
*/
|
||||
public Http2Settings maxConcurrentStreams(int maxConcurrentStreams) {
|
||||
if (maxConcurrentStreams < 0) {
|
||||
throw new IllegalArgumentException("maxConcurrentStreams must be >= 0");
|
||||
}
|
||||
enable(MAX_CONCURRENT_STREAMS_MASK);
|
||||
this.maxConcurrentStreams = maxConcurrentStreams;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not the initialWindowSize value is available.
|
||||
*/
|
||||
public boolean hasInitialWindowSize() {
|
||||
return isEnabled(INITIAL_WINDOW_SIZE_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initial flow control window size or throws {@link NoSuchElementException} if the
|
||||
* value has not been set.
|
||||
*/
|
||||
public int initialWindowSize() {
|
||||
if (!hasInitialWindowSize()) {
|
||||
throw new NoSuchElementException("initialWindowSize");
|
||||
}
|
||||
return initialWindowSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial flow control window size to the specified value.
|
||||
*/
|
||||
public Http2Settings initialWindowSize(int initialWindowSize) {
|
||||
if (initialWindowSize < 0) {
|
||||
throw new IllegalArgumentException("initialWindowSize must be >= 0");
|
||||
}
|
||||
enable(INITIAL_WINDOW_SIZE_MASK);
|
||||
this.initialWindowSize = initialWindowSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not the allowCompressedData value is available.
|
||||
*/
|
||||
public boolean hasAllowCompressedData() {
|
||||
return isEnabled(ALLOW_COMPRESSION_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the endpoint allows compressed data or throws {@link NoSuchElementException} if
|
||||
* the value has not been set.
|
||||
*/
|
||||
public boolean allowCompressedData() {
|
||||
if (!hasAllowCompressedData()) {
|
||||
throw new NoSuchElementException("allowCompressedData");
|
||||
}
|
||||
return allowCompressedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the endpoing allows compressed data.
|
||||
*/
|
||||
public Http2Settings allowCompressedData(boolean allowCompressedData) {
|
||||
enable(ALLOW_COMPRESSION_MASK);
|
||||
this.allowCompressedData = allowCompressedData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites this settings object with the values of the given settings.
|
||||
*
|
||||
* @param source the source that will overwrite the current settings.
|
||||
* @return this object.
|
||||
*/
|
||||
public Http2Settings copy(Http2Settings source) {
|
||||
enabled = source.enabled;
|
||||
allowCompressedData = source.allowCompressedData;
|
||||
initialWindowSize = source.initialWindowSize;
|
||||
maxConcurrentStreams = source.maxConcurrentStreams;
|
||||
maxHeaderTableSize = source.maxHeaderTableSize;
|
||||
pushEnabled = source.pushEnabled;
|
||||
return this;
|
||||
public Http2Settings(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (allowCompressedData ? 1231 : 1237);
|
||||
result = prime * result + enabled;
|
||||
result = prime * result + maxHeaderTableSize;
|
||||
result = prime * result + initialWindowSize;
|
||||
result = prime * result + maxConcurrentStreams;
|
||||
result = prime * result + (pushEnabled ? 1231 : 1237);
|
||||
return result;
|
||||
public Long put(int key, Long value) {
|
||||
verifyStandardSetting(key, value);
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Http2Settings other = (Http2Settings) obj;
|
||||
if (allowCompressedData != other.allowCompressedData) {
|
||||
return false;
|
||||
}
|
||||
if (enabled != other.enabled) {
|
||||
return false;
|
||||
}
|
||||
if (maxHeaderTableSize != other.maxHeaderTableSize) {
|
||||
return false;
|
||||
}
|
||||
if (initialWindowSize != other.initialWindowSize) {
|
||||
return false;
|
||||
}
|
||||
if (maxConcurrentStreams != other.maxConcurrentStreams) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pushEnabled == other.pushEnabled;
|
||||
public Long headerTableSize() {
|
||||
return get(SETTINGS_HEADER_TABLE_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("Http2Settings [");
|
||||
if (hasMaxHeaderTableSize()) {
|
||||
builder.append("maxHeaderTableSize=").append(maxHeaderTableSize).append(',');
|
||||
}
|
||||
if (hasPushEnabled()) {
|
||||
builder.append("pushEnabled=").append(pushEnabled).append(',');
|
||||
}
|
||||
if (hasMaxConcurrentStreams()) {
|
||||
builder.append("maxConcurrentStreams=").append(maxConcurrentStreams).append(',');
|
||||
}
|
||||
if (hasInitialWindowSize()) {
|
||||
builder.append("initialWindowSize=").append(initialWindowSize).append(',');
|
||||
}
|
||||
if (hasAllowCompressedData()) {
|
||||
builder.append("allowCompressedData=").append(allowCompressedData).append(',');
|
||||
}
|
||||
builder.append(']');
|
||||
return builder.toString();
|
||||
public Http2Settings headerTableSize(long value) {
|
||||
put(SETTINGS_HEADER_TABLE_SIZE, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void enable(int mask) {
|
||||
enabled |= mask;
|
||||
public Boolean pushEnabled() {
|
||||
Long value = get(SETTINGS_ENABLE_PUSH);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value != 0L;
|
||||
}
|
||||
|
||||
private boolean isEnabled(int mask) {
|
||||
return (enabled & mask) > 0;
|
||||
public Http2Settings pushEnabled(boolean enabled) {
|
||||
put(SETTINGS_ENABLE_PUSH, enabled? 1L : 0L);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Long maxConcurrentStreams() {
|
||||
return get(SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
}
|
||||
|
||||
public Http2Settings maxConcurrentStreams(long value) {
|
||||
put(SETTINGS_MAX_CONCURRENT_STREAMS, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer initialWindowSize() {
|
||||
Long value = get(SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
public Http2Settings initialWindowSize(int value) {
|
||||
put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Http2Settings copyFrom(Http2Settings settings) {
|
||||
clear();
|
||||
putAll(settings);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void verifyStandardSetting(int key, Long value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
switch (key) {
|
||||
case SETTINGS_HEADER_TABLE_SIZE:
|
||||
if (value < 0L || value > MAX_UNSIGNED_INT) {
|
||||
throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: " + value);
|
||||
}
|
||||
break;
|
||||
case SETTINGS_ENABLE_PUSH:
|
||||
if (value != 0L && value != 1L) {
|
||||
throw new IllegalArgumentException("Setting ENABLE_PUSH is invalid: " + value);
|
||||
}
|
||||
break;
|
||||
case SETTINGS_MAX_CONCURRENT_STREAMS:
|
||||
if (value < 0L || value > MAX_UNSIGNED_INT) {
|
||||
throw new IllegalArgumentException("Setting MAX_CONCURRENT_STREAMS is invalid: " + value);
|
||||
}
|
||||
break;
|
||||
case SETTINGS_INITIAL_WINDOW_SIZE:
|
||||
if (value < 0L || value > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Setting INITIAL_WINDOW_SIZE is invalid: " + value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ public class DefaultHttp2ConnectionTest {
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
server = new DefaultHttp2Connection(true, false);
|
||||
client = new DefaultHttp2Connection(false, false);
|
||||
server = new DefaultHttp2Connection(true);
|
||||
client = new DefaultHttp2Connection(false);
|
||||
}
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
|
@ -15,6 +15,10 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@ -23,15 +27,13 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DefaultHttp2FrameReader} and {@link DefaultHttp2FrameWriter}.
|
||||
*/
|
||||
@ -65,33 +67,33 @@ public class DefaultHttp2FrameIOTest {
|
||||
@Test
|
||||
public void emptyDataShouldRoundtrip() throws Exception {
|
||||
ByteBuf data = Unpooled.EMPTY_BUFFER;
|
||||
writer.writeData(ctx, promise, 1000, data, 0, false, false, false);
|
||||
writer.writeData(ctx, promise, 1000, data, 0, false, false);
|
||||
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false));
|
||||
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataShouldRoundtrip() throws Exception {
|
||||
ByteBuf data = dummyData();
|
||||
writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false, false);
|
||||
writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false);
|
||||
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false));
|
||||
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataWithPaddingShouldRoundtrip() throws Exception {
|
||||
ByteBuf data = dummyData();
|
||||
writer.writeData(ctx, promise, 1, data.retain().duplicate(), 256, true, true, true);
|
||||
writer.writeData(ctx, promise, 1, data.retain().duplicate(), 0xFF, true, true);
|
||||
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(256), eq(true), eq(true), eq(true));
|
||||
verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(0xFF), eq(true), eq(true));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@ -129,10 +131,9 @@ public class DefaultHttp2FrameIOTest {
|
||||
public void settingsShouldStripShouldRoundtrip() throws Exception {
|
||||
Http2Settings settings = new Http2Settings();
|
||||
settings.pushEnabled(true);
|
||||
settings.maxHeaderTableSize(4096);
|
||||
settings.headerTableSize(4096);
|
||||
settings.initialWindowSize(123);
|
||||
settings.maxConcurrentStreams(456);
|
||||
settings.allowCompressedData(false);
|
||||
|
||||
writer.writeSettings(ctx, promise, settings);
|
||||
|
||||
@ -193,35 +194,6 @@ public class DefaultHttp2FrameIOTest {
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void altSvcShouldRoundtrip() throws Exception {
|
||||
writer.writeAltSvc(ctx, promise, 1, MAX_UNSIGNED_INT, MAX_UNSIGNED_SHORT, dummyData(), "host", "origin");
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onAltSvcRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT), eq(MAX_UNSIGNED_SHORT),
|
||||
eq(dummyData()), eq("host"), eq("origin"));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void altSvcWithoutOriginShouldRoundtrip() throws Exception {
|
||||
writer.writeAltSvc(ctx, promise, 1, MAX_UNSIGNED_INT, MAX_UNSIGNED_SHORT, dummyData(), "host", null);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onAltSvcRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT), eq(MAX_UNSIGNED_SHORT),
|
||||
eq(dummyData()), eq("host"), isNull(String.class));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockedShouldRoundtrip() throws Exception {
|
||||
writer.writeBlocked(ctx, promise, 1);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onBlockedRead(eq(ctx), eq(1));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyHeadersShouldRoundtrip() throws Exception {
|
||||
Http2Headers headers = Http2Headers.EMPTY_HEADERS;
|
||||
@ -235,10 +207,10 @@ public class DefaultHttp2FrameIOTest {
|
||||
@Test
|
||||
public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception {
|
||||
Http2Headers headers = Http2Headers.EMPTY_HEADERS;
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 256, true, true);
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true));
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@ -255,10 +227,10 @@ public class DefaultHttp2FrameIOTest {
|
||||
@Test
|
||||
public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception {
|
||||
Http2Headers headers = dummyHeaders();
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 256, true, true);
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true));
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@ -276,10 +248,10 @@ public class DefaultHttp2FrameIOTest {
|
||||
@Test
|
||||
public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception {
|
||||
Http2Headers headers = dummyHeaders();
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 256, true, true);
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256),
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF),
|
||||
eq(true), eq(true));
|
||||
frame.release();
|
||||
}
|
||||
@ -298,10 +270,10 @@ public class DefaultHttp2FrameIOTest {
|
||||
@Test
|
||||
public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception {
|
||||
Http2Headers headers = largeHeaders();
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 256, true, true);
|
||||
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256),
|
||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF),
|
||||
eq(true), eq(true));
|
||||
frame.release();
|
||||
}
|
||||
@ -329,10 +301,10 @@ public class DefaultHttp2FrameIOTest {
|
||||
@Test
|
||||
public void pushPromiseWithPaddingShouldRoundtrip() throws Exception {
|
||||
Http2Headers headers = dummyHeaders();
|
||||
writer.writePushPromise(ctx, promise, 1, 2, headers, 256);
|
||||
writer.writePushPromise(ctx, promise, 1, 2, headers, 0xFF);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(256));
|
||||
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(0xFF));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
@ -349,10 +321,10 @@ public class DefaultHttp2FrameIOTest {
|
||||
@Test
|
||||
public void continuedPushPromiseWithPaddingShouldRoundtrip() throws Exception {
|
||||
Http2Headers headers = largeHeaders();
|
||||
writer.writePushPromise(ctx, promise, 1, 2, headers, 256);
|
||||
writer.writePushPromise(ctx, promise, 1, 2, headers, 0xFF);
|
||||
ByteBuf frame = captureWrite();
|
||||
reader.readFrame(ctx, frame, observer);
|
||||
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(256));
|
||||
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(0xFF));
|
||||
frame.release();
|
||||
}
|
||||
|
||||
|
@ -15,17 +15,22 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http2.Http2InboundFlowController.FrameWriter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultHttp2InboundFlowController}.
|
||||
*/
|
||||
@ -46,7 +51,7 @@ public class DefaultHttp2InboundFlowControllerTest {
|
||||
public void setup() throws Http2Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
connection = new DefaultHttp2Connection(false, false);
|
||||
connection = new DefaultHttp2Connection(false);
|
||||
controller = new DefaultHttp2InboundFlowController(connection);
|
||||
|
||||
connection.local().createStream(STREAM_ID, false);
|
||||
@ -60,14 +65,14 @@ public class DefaultHttp2InboundFlowControllerTest {
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void connectionFlowControlExceededShouldThrow() throws Http2Exception {
|
||||
applyFlowControl(DEFAULT_FLOW_CONTROL_WINDOW_SIZE + 1, true);
|
||||
applyFlowControl(DEFAULT_WINDOW_SIZE + 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void halfWindowRemainingShouldUpdateConnectionWindow() throws Http2Exception {
|
||||
int dataSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2 + 1;
|
||||
int newWindow = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - dataSize;
|
||||
int windowDelta = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - newWindow;
|
||||
int dataSize = DEFAULT_WINDOW_SIZE / 2 + 1;
|
||||
int newWindow = DEFAULT_WINDOW_SIZE - dataSize;
|
||||
int windowDelta = DEFAULT_WINDOW_SIZE - newWindow;
|
||||
|
||||
// Set end-of-stream on the frame, so no window update will be sent for the stream.
|
||||
applyFlowControl(dataSize, true);
|
||||
@ -76,8 +81,8 @@ public class DefaultHttp2InboundFlowControllerTest {
|
||||
|
||||
@Test
|
||||
public void halfWindowRemainingShouldUpdateAllWindows() throws Http2Exception {
|
||||
int dataSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2 + 1;
|
||||
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||
int dataSize = DEFAULT_WINDOW_SIZE / 2 + 1;
|
||||
int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
int windowDelta = getWindowDelta(initialWindowSize, initialWindowSize, dataSize);
|
||||
|
||||
// Don't set end-of-stream so we'll get a window update for the stream as well.
|
||||
@ -89,7 +94,7 @@ public class DefaultHttp2InboundFlowControllerTest {
|
||||
@Test
|
||||
public void initialWindowUpdateShouldAllowMoreFrames() throws Http2Exception {
|
||||
// Send a frame that takes up the entire window.
|
||||
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||
int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
applyFlowControl(initialWindowSize, false);
|
||||
|
||||
// Update the initial window size to allow another frame.
|
||||
@ -113,8 +118,7 @@ public class DefaultHttp2InboundFlowControllerTest {
|
||||
|
||||
private void applyFlowControl(int dataSize, boolean endOfStream) throws Http2Exception {
|
||||
ByteBuf buf = dummyData(dataSize);
|
||||
controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false,
|
||||
false, frameWriter);
|
||||
controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false, frameWriter);
|
||||
buf.release();
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void setup() throws Http2Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
connection = new DefaultHttp2Connection(false, false);
|
||||
connection = new DefaultHttp2Connection(false);
|
||||
controller = new DefaultHttp2OutboundFlowController(connection);
|
||||
|
||||
connection.local().createStream(STREAM_A, false);
|
||||
@ -142,7 +142,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void connectionWindowUpdateShouldSendFrame() throws Http2Exception {
|
||||
// Set the connection window size to zero.
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
ByteBuf data = dummyData(10);
|
||||
send(STREAM_A, data);
|
||||
@ -162,7 +162,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void connectionWindowUpdateShouldSendPartialFrame() throws Http2Exception {
|
||||
// Set the connection window size to zero.
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
ByteBuf data = dummyData(10);
|
||||
send(STREAM_A, data);
|
||||
@ -183,7 +183,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
@Test
|
||||
public void streamWindowUpdateShouldSendFrame() throws Http2Exception {
|
||||
// Set the stream window size to zero.
|
||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
ByteBuf data = dummyData(10);
|
||||
send(STREAM_A, data);
|
||||
@ -202,7 +202,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
@Test
|
||||
public void streamWindowUpdateShouldSendPartialFrame() throws Http2Exception {
|
||||
// Set the stream window size to zero.
|
||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
ByteBuf data = dummyData(10);
|
||||
send(STREAM_A, data);
|
||||
@ -235,10 +235,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void blockedStreamShouldSpreadDataToChildren() throws Http2Exception {
|
||||
// Block the connection
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Block stream A
|
||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Try sending 10 bytes on each stream. They will be pending until we free up the
|
||||
// connection.
|
||||
@ -287,10 +287,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void childrenShouldNotSendDataUntilParentBlocked() throws Http2Exception {
|
||||
// Block the connection
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Block stream B
|
||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Send 10 bytes to each.
|
||||
send(STREAM_A, dummyData(10));
|
||||
@ -330,10 +330,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void parentShouldWaterFallDataToChildren() throws Http2Exception {
|
||||
// Block the connection
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Block stream B
|
||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Only send 5 to A so that it will allow data from its children.
|
||||
send(STREAM_A, dummyData(5));
|
||||
@ -392,10 +392,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void reprioritizeShouldAdjustOutboundFlow() throws Http2Exception {
|
||||
// Block the connection
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Block stream B
|
||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Send 10 bytes to each.
|
||||
send(STREAM_A, dummyData(10));
|
||||
@ -437,7 +437,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void writeShouldPreferHighestWeight() throws Http2Exception {
|
||||
// Block the connection
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Root the streams at the connection and assign weights.
|
||||
setPriority(STREAM_A, 0, (short) 50, false);
|
||||
@ -501,7 +501,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
||||
public void samePriorityShouldWriteEqualData() throws Http2Exception {
|
||||
// Block the connection
|
||||
controller
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||
|
||||
// Root the streams at the connection with the same weights.
|
||||
setPriority(STREAM_A, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
||||
|
@ -15,6 +15,34 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
|
||||
import static io.netty.buffer.Unpooled.copiedBuffer;
|
||||
import static io.netty.buffer.Unpooled.wrappedBuffer;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
||||
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.Http2Error.NO_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.EMPTY_HEADERS;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_LOCAL;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
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.anyLong;
|
||||
import static org.mockito.Matchers.anyShort;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
@ -22,6 +50,9 @@ import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.DefaultChannelPromise;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -29,18 +60,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
import static io.netty.handler.codec.http2.Http2Error.*;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.*;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.*;
|
||||
import static io.netty.util.CharsetUtil.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link DelegatingHttp2ConnectionHandlerTest} and its base class
|
||||
* {@link AbstractHttp2ConnectionHandler}.
|
||||
@ -123,16 +142,14 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
||||
|
||||
// Simulate activation of the handler to force writing the initial settings.
|
||||
Http2Settings settings = new Http2Settings();
|
||||
settings.allowCompressedData(true);
|
||||
settings.initialWindowSize(10);
|
||||
settings.pushEnabled(true);
|
||||
settings.maxConcurrentStreams(100);
|
||||
settings.maxHeaderTableSize(200);
|
||||
when(local.allowCompressedData()).thenReturn(true);
|
||||
settings.headerTableSize(200);
|
||||
when(inboundFlow.initialInboundWindowSize()).thenReturn(10);
|
||||
when(local.allowPushTo()).thenReturn(true);
|
||||
when(remote.maxStreams()).thenReturn(100);
|
||||
when(reader.maxHeaderTableSize()).thenReturn(200);
|
||||
when(reader.maxHeaderTableSize()).thenReturn(200L);
|
||||
handler.handlerAdded(ctx);
|
||||
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
|
||||
|
||||
@ -229,40 +246,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
||||
@Test
|
||||
public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception {
|
||||
when(remote.isGoAwayReceived()).thenReturn(true);
|
||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true, true);
|
||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true);
|
||||
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
||||
eq(true), eq(true), eq(true), any(Http2InboundFlowController.FrameWriter.class));
|
||||
eq(true), eq(true), any(Http2InboundFlowController.FrameWriter.class));
|
||||
|
||||
// Verify that the event was absorbed and not propagated to the oberver.
|
||||
verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(),
|
||||
verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(),
|
||||
anyBoolean(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataReadWithEndOfStreamShouldCloseRemoteSide() throws Exception {
|
||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, false, false);
|
||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, false);
|
||||
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
||||
eq(true), eq(false), eq(false), any(Http2InboundFlowController.FrameWriter.class));
|
||||
eq(true), eq(false), any(Http2InboundFlowController.FrameWriter.class));
|
||||
verify(stream).closeRemoteSide();
|
||||
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true),
|
||||
eq(false), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataReadWithShouldAllowCompression() throws Exception {
|
||||
when(local.allowCompressedData()).thenReturn(true);
|
||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, false, false, true);
|
||||
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
||||
eq(false), eq(false), eq(true), any(Http2InboundFlowController.FrameWriter.class));
|
||||
verify(stream, never()).closeRemoteSide();
|
||||
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(false),
|
||||
eq(false), eq(true));
|
||||
}
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void dataReadShouldDisallowCompression() throws Exception {
|
||||
when(local.allowCompressedData()).thenReturn(false);
|
||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, false, false, true);
|
||||
eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -421,14 +421,12 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
||||
settings.pushEnabled(true);
|
||||
settings.initialWindowSize(123);
|
||||
settings.maxConcurrentStreams(456);
|
||||
settings.allowCompressedData(true);
|
||||
settings.maxHeaderTableSize(789);
|
||||
settings.headerTableSize(789);
|
||||
decode().onSettingsRead(ctx, settings);
|
||||
verify(remote).allowPushTo(true);
|
||||
verify(outboundFlow).initialOutboundWindowSize(123);
|
||||
verify(local).maxStreams(456);
|
||||
assertTrue(handler.settings().allowCompressedData());
|
||||
verify(writer).maxHeaderTableSize(789);
|
||||
verify(writer).maxHeaderTableSize(789L);
|
||||
// Take into account the time this was called during setup().
|
||||
verify(writer, times(2)).writeSettingsAck(eq(ctx), eq(promise));
|
||||
verify(observer).onSettingsRead(eq(ctx), eq(settings));
|
||||
@ -441,22 +439,6 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
||||
verify(observer).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER));
|
||||
}
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void serverAltSvcReadShouldThrow() throws Exception {
|
||||
when(connection.isServer()).thenReturn(true);
|
||||
decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, EMPTY_BUFFER, "www.example.com", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientAltSvcReadShouldNotifyObserver() throws Exception {
|
||||
String host = "www.host.com";
|
||||
String origin = "www.origin.com";
|
||||
when(connection.isServer()).thenReturn(false);
|
||||
decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
|
||||
verify(observer).onAltSvcRead(eq(ctx), eq(STREAM_ID), eq(1L), eq(2), eq(EMPTY_BUFFER),
|
||||
eq(host), eq(origin));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataWriteAfterGoAwayShouldFail() throws Exception {
|
||||
when(connection.isGoAway()).thenReturn(true);
|
||||
@ -464,21 +446,6 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
||||
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataWriteShouldDisallowCompression() throws Exception {
|
||||
when(local.allowCompressedData()).thenReturn(false);
|
||||
ChannelFuture future = handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, true);
|
||||
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataWriteShouldAllowCompression() throws Exception {
|
||||
when(remote.allowCompressedData()).thenReturn(true);
|
||||
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, true);
|
||||
verify(outboundFlow).sendFlowControlled(eq(STREAM_ID), eq(dummyData()), eq(0), eq(false),
|
||||
eq(false), eq(true), any(Http2OutboundFlowController.FrameWriter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataWriteShouldSucceed() throws Exception {
|
||||
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, false);
|
||||
@ -600,44 +567,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
||||
@Test
|
||||
public void settingsWriteShouldNotUpdateSettings() throws Exception {
|
||||
Http2Settings settings = new Http2Settings();
|
||||
settings.allowCompressedData(false);
|
||||
settings.initialWindowSize(100);
|
||||
settings.pushEnabled(false);
|
||||
settings.maxConcurrentStreams(1000);
|
||||
settings.maxHeaderTableSize(2000);
|
||||
settings.headerTableSize(2000);
|
||||
handler.writeSettings(ctx, promise, settings);
|
||||
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
|
||||
// Verify that application of local settings must not be done when it is dispatched.
|
||||
verify(local, never()).allowCompressedData(eq(false));
|
||||
verify(inboundFlow, never()).initialInboundWindowSize(eq(100));
|
||||
verify(local, never()).allowPushTo(eq(false));
|
||||
verify(remote, never()).maxStreams(eq(1000));
|
||||
verify(reader, never()).maxHeaderTableSize(eq(2000));
|
||||
verify(reader, never()).maxHeaderTableSize(eq(2000L));
|
||||
// Verify that settings values are applied on the reception of SETTINGS ACK
|
||||
decode().onSettingsAckRead(ctx);
|
||||
verify(local).allowCompressedData(eq(false));
|
||||
verify(inboundFlow).initialInboundWindowSize(eq(100));
|
||||
verify(local).allowPushTo(eq(false));
|
||||
verify(remote).maxStreams(eq(1000));
|
||||
verify(reader).maxHeaderTableSize(eq(2000));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientWriteAltSvcShouldThrow() throws Exception {
|
||||
when(connection.isServer()).thenReturn(false);
|
||||
ChannelFuture future = handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, EMPTY_BUFFER,
|
||||
"www.example.com", null);
|
||||
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverWriteAltSvcShouldSucceed() throws Exception {
|
||||
String host = "www.host.com";
|
||||
String origin = "www.origin.com";
|
||||
when(connection.isServer()).thenReturn(true);
|
||||
handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
|
||||
verify(writer).writeAltSvc(eq(ctx), eq(promise), eq(STREAM_ID), eq(1L), eq(2),
|
||||
eq(EMPTY_BUFFER), eq(host), eq(origin));
|
||||
verify(reader).maxHeaderTableSize(eq(2000L));
|
||||
}
|
||||
|
||||
private static ByteBuf dummyData() {
|
||||
|
@ -129,7 +129,7 @@ public class Http2ConnectionRoundtripTest {
|
||||
anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false),
|
||||
eq(false));
|
||||
verify(serverObserver, times(NUM_STREAMS)).onDataRead(any(ChannelHandlerContext.class),
|
||||
anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true), eq(false));
|
||||
anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true));
|
||||
}
|
||||
|
||||
private void awaitRequests() throws Exception {
|
||||
@ -152,10 +152,10 @@ public class Http2ConnectionRoundtripTest {
|
||||
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed)
|
||||
boolean endOfStream, boolean endOfSegment)
|
||||
throws Http2Exception {
|
||||
serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
|
||||
endOfSegment, compressed);
|
||||
endOfSegment);
|
||||
requestLatch.countDown();
|
||||
}
|
||||
|
||||
@ -235,16 +235,9 @@ public class Http2ConnectionRoundtripTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
||||
serverObserver
|
||||
.onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host, origin);
|
||||
requestLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
||||
serverObserver.onBlockedRead(ctx, streamId);
|
||||
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
|
||||
Http2Flags flags, ByteBuf payload) {
|
||||
serverObserver.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||
requestLatch.countDown();
|
||||
}
|
||||
|
||||
|
@ -116,12 +116,12 @@ public class Http2FrameRoundtripTest {
|
||||
@Override
|
||||
public void run() {
|
||||
frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF,
|
||||
Unpooled.copiedBuffer(text.getBytes()), 100, true, false, false);
|
||||
Unpooled.copiedBuffer(text.getBytes()), 100, true, false);
|
||||
}
|
||||
});
|
||||
awaitRequests();
|
||||
verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
|
||||
dataCaptor.capture(), eq(100), eq(true), eq(false), eq(false));
|
||||
dataCaptor.capture(), eq(100), eq(true), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -231,10 +231,9 @@ public class Http2FrameRoundtripTest {
|
||||
@Test
|
||||
public void settingsFrameShouldMatch() throws Exception {
|
||||
final Http2Settings settings = new Http2Settings();
|
||||
settings.allowCompressedData(true);
|
||||
settings.initialWindowSize(10);
|
||||
settings.maxConcurrentStreams(1000);
|
||||
settings.maxHeaderTableSize(4096);
|
||||
settings.headerTableSize(4096);
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -274,7 +273,7 @@ public class Http2FrameRoundtripTest {
|
||||
frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false,
|
||||
0, false, false);
|
||||
frameWriter.writeData(ctx(), newPromise(), i,
|
||||
Unpooled.copiedBuffer(text.getBytes()), 0, true, true, false);
|
||||
Unpooled.copiedBuffer(text.getBytes()), 0, true, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -310,10 +309,10 @@ public class Http2FrameRoundtripTest {
|
||||
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
|
||||
int padding, boolean endOfStream, boolean endOfSegment, boolean compressed)
|
||||
int padding, boolean endOfStream, boolean endOfSegment)
|
||||
throws Http2Exception {
|
||||
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
|
||||
endOfSegment, compressed);
|
||||
endOfSegment);
|
||||
requestLatch.countDown();
|
||||
}
|
||||
|
||||
@ -400,18 +399,9 @@ public class Http2FrameRoundtripTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge,
|
||||
int port, ByteBuf protocolId, String host, String origin)
|
||||
throws Http2Exception {
|
||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host,
|
||||
origin);
|
||||
requestLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId)
|
||||
throws Http2Exception {
|
||||
observer.onBlockedRead(ctx, streamId);
|
||||
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
|
||||
Http2Flags flags, ByteBuf payload) {
|
||||
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||
requestLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
@ -16,11 +16,9 @@
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -37,105 +35,23 @@ public class Http2SettingsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allValuesShouldBeNotSet() {
|
||||
assertFalse(settings.hasAllowCompressedData());
|
||||
assertFalse(settings.hasMaxHeaderTableSize());
|
||||
assertFalse(settings.hasInitialWindowSize());
|
||||
assertFalse(settings.hasMaxConcurrentStreams());
|
||||
assertFalse(settings.hasPushEnabled());
|
||||
public void standardSettingsShouldBeNotSet() {
|
||||
assertEquals(0, settings.size());
|
||||
assertNull(settings.headerTableSize());
|
||||
assertNull(settings.initialWindowSize());
|
||||
assertNull(settings.maxConcurrentStreams());
|
||||
assertNull(settings.pushEnabled());
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void unsetAllowCompressedDataShouldThrow() {
|
||||
// Set everything else.
|
||||
settings.maxHeaderTableSize(1);
|
||||
@Test
|
||||
public void standardSettingsShouldBeSet() {
|
||||
settings.initialWindowSize(1);
|
||||
settings.maxConcurrentStreams(1);
|
||||
settings.maxConcurrentStreams(2);
|
||||
settings.pushEnabled(true);
|
||||
|
||||
settings.allowCompressedData();
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void unsetHeaderTableSizeShouldThrow() {
|
||||
// Set everything else.
|
||||
settings.allowCompressedData(true);
|
||||
settings.initialWindowSize(1);
|
||||
settings.maxConcurrentStreams(1);
|
||||
settings.pushEnabled(true);
|
||||
|
||||
settings.maxHeaderTableSize();
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void unsetInitialWindowSizeShouldThrow() {
|
||||
// Set everything else.
|
||||
settings.allowCompressedData(true);
|
||||
settings.maxHeaderTableSize(1);
|
||||
settings.maxConcurrentStreams(1);
|
||||
settings.pushEnabled(true);
|
||||
|
||||
settings.initialWindowSize();
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void unsetMaxConcurrentStreamsShouldThrow() {
|
||||
// Set everything else.
|
||||
settings.allowCompressedData(true);
|
||||
settings.maxHeaderTableSize(1);
|
||||
settings.initialWindowSize(1);
|
||||
settings.pushEnabled(true);
|
||||
|
||||
settings.maxConcurrentStreams();
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void unsetPushEnabledShouldThrow() {
|
||||
// Set everything else.
|
||||
settings.allowCompressedData(true);
|
||||
settings.maxHeaderTableSize(1);
|
||||
settings.initialWindowSize(1);
|
||||
settings.maxConcurrentStreams(1);
|
||||
|
||||
settings.pushEnabled();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowCompressedDataShouldBeSet() {
|
||||
settings.allowCompressedData(true);
|
||||
assertTrue(settings.hasAllowCompressedData());
|
||||
assertTrue(settings.allowCompressedData());
|
||||
settings.allowCompressedData(false);
|
||||
assertFalse(settings.allowCompressedData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerTableSizeShouldBeSet() {
|
||||
settings.maxHeaderTableSize(123);
|
||||
assertTrue(settings.hasMaxHeaderTableSize());
|
||||
assertEquals(123, settings.maxHeaderTableSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialWindowSizeShouldBeSet() {
|
||||
settings.initialWindowSize(123);
|
||||
assertTrue(settings.hasInitialWindowSize());
|
||||
assertEquals(123, settings.initialWindowSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxConcurrentStreamsShouldBeSet() {
|
||||
settings.maxConcurrentStreams(123);
|
||||
assertTrue(settings.hasMaxConcurrentStreams());
|
||||
assertEquals(123, settings.maxConcurrentStreams());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pushEnabledShouldBeSet() {
|
||||
settings.pushEnabled(true);
|
||||
assertTrue(settings.hasPushEnabled());
|
||||
settings.headerTableSize(3);
|
||||
assertEquals(1, (int) settings.initialWindowSize());
|
||||
assertEquals(2L, (long) settings.maxConcurrentStreams());
|
||||
assertTrue(settings.pushEnabled());
|
||||
settings.pushEnabled(false);
|
||||
assertFalse(settings.pushEnabled());
|
||||
assertEquals(3L, (long) settings.headerTableSize());
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +247,42 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
|
||||
return outValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
for (Entry<V> entry : entries()) {
|
||||
V value = entry.value();
|
||||
int hash = value == null ? 0 : value.hashCode();
|
||||
result = prime * result + hash;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj || obj == null || getClass() != obj.getClass()) {
|
||||
return true;
|
||||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
IntObjectHashMap other = (IntObjectHashMap) obj;
|
||||
if (size != other.size) {
|
||||
return false;
|
||||
}
|
||||
for (Entry<V> entry : entries()) {
|
||||
V value = entry.value();
|
||||
Object otherValue = other.get(entry.key());
|
||||
if (value == null) {
|
||||
if (otherValue != null || !other.containsKey(entry.key())) {
|
||||
return false;
|
||||
}
|
||||
} else if (!value.equals(otherValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the occupied entries from the source to the target array.
|
||||
*/
|
||||
|
@ -57,7 +57,7 @@ public class Http2ClientConnectionHandler extends AbstractHttp2ConnectionHandler
|
||||
private ByteBuf collectedData;
|
||||
|
||||
public Http2ClientConnectionHandler(ChannelPromise initPromise, ChannelPromise responsePromise) {
|
||||
this(initPromise, responsePromise, new DefaultHttp2Connection(false, false));
|
||||
this(initPromise, responsePromise, new DefaultHttp2Connection(false));
|
||||
}
|
||||
|
||||
private Http2ClientConnectionHandler(ChannelPromise initPromise,
|
||||
@ -119,7 +119,7 @@ public class Http2ClientConnectionHandler extends AbstractHttp2ConnectionHandler
|
||||
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
||||
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||
|
||||
// Copy the data into the buffer.
|
||||
int available = data.readableBytes();
|
||||
|
@ -47,7 +47,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
|
||||
static final byte[] RESPONSE_BYTES = "Hello World".getBytes(CharsetUtil.UTF_8);
|
||||
|
||||
public HelloWorldHttp2Handler() {
|
||||
this(new DefaultHttp2Connection(true, false));
|
||||
this(new DefaultHttp2Connection(true));
|
||||
}
|
||||
|
||||
private HelloWorldHttp2Handler(Http2Connection connection) {
|
||||
@ -77,7 +77,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
|
||||
*/
|
||||
@Override
|
||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
||||
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||
if (endOfStream) {
|
||||
sendResponse(ctx(), streamId);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user