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:
nmittler 2014-07-07 09:34:30 -07:00 committed by Norman Maurer
parent 58cc16d106
commit 67159e7119
38 changed files with 693 additions and 1578 deletions

View File

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

View File

@ -15,6 +15,21 @@
package io.netty.handler.codec.http2; 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.buffer.ByteBuf;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
@ -27,11 +42,6 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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 * 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 * 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; private ChannelFutureListener closeListener;
protected AbstractHttp2ConnectionHandler(boolean server) { protected AbstractHttp2ConnectionHandler(boolean server) {
this(server, false); this(new DefaultHttp2Connection(server));
}
protected AbstractHttp2ConnectionHandler(boolean server, boolean allowCompression) {
this(new DefaultHttp2Connection(server, allowCompression));
} }
protected AbstractHttp2ConnectionHandler(Http2Connection connection) { protected AbstractHttp2ConnectionHandler(Http2Connection connection) {
@ -201,10 +207,9 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
*/ */
public final Http2Settings settings() { public final Http2Settings settings() {
Http2Settings settings = new Http2Settings(); Http2Settings settings = new Http2Settings();
settings.allowCompressedData(connection.local().allowCompressedData());
settings.initialWindowSize(inboundFlow.initialInboundWindowSize()); settings.initialWindowSize(inboundFlow.initialInboundWindowSize());
settings.maxConcurrentStreams(connection.remote().maxStreams()); settings.maxConcurrentStreams(connection.remote().maxStreams());
settings.maxHeaderTableSize(frameReader.maxHeaderTableSize()); settings.headerTableSize(frameReader.maxHeaderTableSize());
if (!connection.isServer()) { if (!connection.isServer()) {
// Only set the pushEnabled flag if this is a client endpoint. // Only set the pushEnabled flag if this is a client endpoint.
settings.pushEnabled(connection.local().allowPushTo()); settings.pushEnabled(connection.local().allowPushTo());
@ -217,7 +222,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
*/ */
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception { boolean endOfStream, boolean endOfSegment) throws Http2Exception {
} }
/** /**
@ -227,7 +232,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
*/ */
@Override @Override
public final void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public final void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, boolean endSegment) { int padding, boolean endStream, boolean endSegment) throws Http2Exception {
} }
/** /**
@ -312,15 +317,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
* Default implementation. Does nothing. * Default implementation. Does nothing.
*/ */
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port, public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf protocolId, String host, String origin) throws Http2Exception { ByteBuf payload) {
}
/**
* Default implementation. Does nothing.
*/
@Override
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
} }
protected final ChannelHandlerContext ctx() { protected final ChannelHandlerContext ctx() {
@ -346,10 +344,6 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
throw protocolError("Sending data after connection going away."); 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); Http2Stream stream = connection.requireStream(streamId);
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE); 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."); 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"); 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 @Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception { throws Exception {
@ -724,27 +710,28 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
* Applies settings received from the remote endpoint. * Applies settings received from the remote endpoint.
*/ */
private void applyRemoteSettings(Http2Settings settings) throws Http2Exception { private void applyRemoteSettings(Http2Settings settings) throws Http2Exception {
if (settings.hasPushEnabled()) { Boolean pushEnabled = settings.pushEnabled();
if (pushEnabled != null) {
if (!connection.isServer()) { if (!connection.isServer()) {
throw protocolError("Client received SETTINGS frame with ENABLE_PUSH specified"); throw protocolError("Client received SETTINGS frame with ENABLE_PUSH specified");
} }
connection.remote().allowPushTo(settings.pushEnabled()); connection.remote().allowPushTo(pushEnabled);
} }
if (settings.hasAllowCompressedData()) { Long maxConcurrentStreams = settings.maxConcurrentStreams();
connection.remote().allowCompressedData(settings.allowCompressedData()); if (maxConcurrentStreams != null) {
int value = (int) Math.min(maxConcurrentStreams, Integer.MAX_VALUE);
connection.local().maxStreams(value);
} }
if (settings.hasMaxConcurrentStreams()) { Long headerTableSize = settings.headerTableSize();
connection.local().maxStreams(settings.maxConcurrentStreams()); if (headerTableSize != null) {
frameWriter.maxHeaderTableSize(headerTableSize);
} }
if (settings.hasMaxHeaderTableSize()) { Integer initialWindowSize = settings.initialWindowSize();
frameWriter.maxHeaderTableSize(settings.maxHeaderTableSize()); if (initialWindowSize != null) {
} outboundFlow.initialOutboundWindowSize(initialWindowSize);
if (settings.hasInitialWindowSize()) {
outboundFlow.initialOutboundWindowSize(settings.initialWindowSize());
} }
} }
@ -769,23 +756,19 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
@Override @Override
public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception { boolean endOfStream, boolean endOfSegment) throws Http2Exception {
verifyPrefaceReceived(); 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 // Check if we received a data frame for a stream which is half-closed
Http2Stream stream = connection.requireStream(streamId); Http2Stream stream = connection.requireStream(streamId);
stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL); stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
// Apply flow control. // Apply flow control.
inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, endOfSegment, inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, endOfSegment,
compressed, new Http2InboundFlowController.FrameWriter() { new Http2InboundFlowController.FrameWriter() {
@Override @Override
public void writeFrame(int streamId, int windowSizeIncrement) { public void writeFrame(int streamId, int windowSizeIncrement)
throws Http2Exception {
frameWriter.writeWindowUpdate(ctx, ctx.newPromise(), streamId, frameWriter.writeWindowUpdate(ctx, ctx.newPromise(), streamId,
windowSizeIncrement); windowSizeIncrement);
} }
@ -803,7 +786,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
} }
AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream, 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. * Applies settings sent from the local endpoint.
*/ */
private void applyLocalSettings(Http2Settings settings) throws Http2Exception { private void applyLocalSettings(Http2Settings settings) throws Http2Exception {
if (settings.hasPushEnabled()) { Boolean pushEnabled = settings.pushEnabled();
if (pushEnabled != null) {
if (connection.isServer()) { if (connection.isServer()) {
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified"); throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
} }
connection.local().allowPushTo(settings.pushEnabled()); connection.local().allowPushTo(pushEnabled);
} }
if (settings.hasAllowCompressedData()) { Long maxConcurrentStreams = settings.maxConcurrentStreams();
connection.local().allowCompressedData(settings.allowCompressedData()); if (maxConcurrentStreams != null) {
int value = (int) Math.min(maxConcurrentStreams, Integer.MAX_VALUE);
connection.remote().maxStreams(value);
} }
if (settings.hasMaxConcurrentStreams()) { Long headerTableSize = settings.headerTableSize();
connection.remote().maxStreams(settings.maxConcurrentStreams()); if (headerTableSize != null) {
frameReader.maxHeaderTableSize(headerTableSize);
} }
if (settings.hasMaxHeaderTableSize()) { Integer initialWindowSize = settings.initialWindowSize();
frameReader.maxHeaderTableSize(settings.maxHeaderTableSize()); if (initialWindowSize != null) {
} inboundFlow.initialInboundWindowSize(initialWindowSize);
if (settings.hasInitialWindowSize()) {
inboundFlow.initialInboundWindowSize(settings.initialWindowSize());
} }
} }
@ -1045,31 +1029,9 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port, public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf protocolId, String host, String origin) throws Http2Exception { ByteBuf payload) {
if (connection.isServer()) { AbstractHttp2ConnectionHandler.this.onUnknownFrame(ctx, frameType, streamId, flags, payload);
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);
} }
/** /**
@ -1160,12 +1122,11 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
// Write the frame. // Write the frame.
ChannelFuture future = ChannelFuture future =
frameWriter.writeData(ctx, chunkPromise, streamId, data, frameWriter.writeData(ctx, chunkPromise, streamId, data, padding, endStream,
padding, endStream, endSegment, compressed); endSegment);
// Close the connection on write failures that leave the outbound // Close the connection on write failures that leave the outbound
// flow // flow control window in a corrupt state.
// control window in a corrupt state.
future.addListener(new ChannelFutureListener() { future.addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) public void operationComplete(ChannelFuture future)

View File

@ -45,39 +45,28 @@ public class DefaultHttp2Connection implements Http2Connection {
private final Http2StreamRemovalPolicy removalPolicy; 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. * @param server whether or not this end-point is the server-side of the HTTP/2 connection.
*/ */
public DefaultHttp2Connection(boolean server) { public DefaultHttp2Connection(boolean server) {
this(server, false); this(server, immediateRemovalPolicy());
}
/**
* 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());
} }
/** /**
* Creates a new connection with the given settings. * 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 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. * @param removalPolicy the policy to be used for removal of closed stream.
*/ */
public DefaultHttp2Connection(boolean server, boolean allowCompressedData, public DefaultHttp2Connection(boolean server,
Http2StreamRemovalPolicy removalPolicy) { Http2StreamRemovalPolicy removalPolicy) {
if (removalPolicy == null) { if (removalPolicy == null) {
throw new NullPointerException("removalPolicy"); throw new NullPointerException("removalPolicy");
} }
this.removalPolicy = removalPolicy; this.removalPolicy = removalPolicy;
localEndpoint = new DefaultEndpoint(server, allowCompressedData); localEndpoint = new DefaultEndpoint(server);
remoteEndpoint = new DefaultEndpoint(!server, false); remoteEndpoint = new DefaultEndpoint(!server);
// Tell the removal policy how to remove a stream from this connection. // Tell the removal policy how to remove a stream from this connection.
removalPolicy.setAction(new Action() { removalPolicy.setAction(new Action() {
@ -600,8 +589,7 @@ public class DefaultHttp2Connection implements Http2Connection {
private int nextStreamId; private int nextStreamId;
private int lastStreamCreated; private int lastStreamCreated;
private int lastKnownStream = -1; private int lastKnownStream = -1;
private boolean pushToAllowed; private boolean pushToAllowed = true;
private boolean allowCompressedData;
/** /**
* The maximum number of active streams allowed to be created by this endpoint. * 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; private int numActiveStreams;
DefaultEndpoint(boolean server, boolean allowCompressedData) { DefaultEndpoint(boolean server) {
this.allowCompressedData = allowCompressedData;
this.server = server; this.server = server;
// Determine the starting stream ID for this endpoint. Client-initiated streams // Determine the starting stream ID for this endpoint. Client-initiated streams
@ -738,16 +725,6 @@ public class DefaultHttp2Connection implements Http2Connection {
this.maxStreams = maxStreams; this.maxStreams = maxStreams;
} }
@Override
public boolean allowCompressedData() {
return allowCompressedData;
}
@Override
public void allowCompressedData(boolean allow) {
allowCompressedData = allow;
}
@Override @Override
public int lastStreamCreated() { public int lastStreamCreated() {
return lastStreamCreated; return lastStreamCreated;

View File

@ -15,14 +15,28 @@
package io.netty.handler.codec.http2; 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.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext; 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. * 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 final Http2HeadersDecoder headersDecoder;
private State state = State.FRAME_HEADER; private State state = State.FRAME_HEADER;
private Http2FrameType frameType; private byte frameType;
private int streamId; private int streamId;
private Http2Flags flags; private Http2Flags flags;
private int payloadLength; private int payloadLength;
@ -52,12 +66,12 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
} }
@Override @Override
public void maxHeaderTableSize(int max) { public void maxHeaderTableSize(long max) {
headersDecoder.maxHeaderTableSize(max); headersDecoder.maxHeaderTableSize((int) Math.min(max, Integer.MAX_VALUE));
} }
@Override @Override
public int maxHeaderTableSize() { public long maxHeaderTableSize() {
return headersDecoder.maxHeaderTableSize(); return headersDecoder.maxHeaderTableSize();
} }
@ -120,7 +134,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
// Read the header and prepare the unmarshaller to read the frame. // Read the header and prepare the unmarshaller to read the frame.
payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK; payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK;
frameType = Http2FrameType.forTypeCode(in.readUnsignedByte()); frameType = in.readByte();
flags = new Http2Flags(in.readUnsignedByte()); flags = new Http2Flags(in.readUnsignedByte());
streamId = readUnsignedInt(in); streamId = readUnsignedInt(in);
@ -155,22 +169,16 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
case CONTINUATION: case CONTINUATION:
verifyContinuationFrame(); verifyContinuationFrame();
break; break;
case ALT_SVC:
verifyAltSvcFrame();
break;
case BLOCKED:
verifyBlockedFrame();
break;
default: default:
throw protocolError("Unsupported frame type: %s", frameType); // Unknown frame type, could be an extension.
break;
} }
// Start reading the payload for the frame. // Start reading the payload for the frame.
state = State.FRAME_PAYLOAD; state = State.FRAME_PAYLOAD;
} }
private void private void processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
throws Http2Exception { throws Http2Exception {
if (in.readableBytes() < payloadLength) { if (in.readableBytes() < payloadLength) {
// Wait until the entire payload has been read. // Wait until the entire payload has been read.
@ -212,15 +220,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
case CONTINUATION: case CONTINUATION:
readContinuationFrame(payload, observer); readContinuationFrame(payload, observer);
break; break;
case ALT_SVC:
readAltSvcFrame(ctx, payload, observer);
break;
case BLOCKED:
observer.onBlockedRead(ctx, streamId);
break;
default: default:
// Should never happen. readUnknownFrame(ctx, payload, observer);
throw protocolError("Unsupported frame type: %s", frameType); break;
} }
// Go back to reading the next frame header. // Go back to reading the next frame header.
@ -231,10 +233,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
verifyNotProcessingHeaders(); verifyNotProcessingHeaders();
verifyPayloadLength(payloadLength); verifyPayloadLength(payloadLength);
if (!flags.isPaddingLengthValid()) { if (payloadLength < flags.getPaddingPresenceFieldLength()) {
throw protocolError("Pad high is set but pad low is not");
}
if (payloadLength < flags.getNumPaddingLengthBytes()) {
throw protocolError("Frame length %d too small.", payloadLength); throw protocolError("Frame length %d too small.", payloadLength);
} }
} }
@ -243,18 +242,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
verifyNotProcessingHeaders(); verifyNotProcessingHeaders();
verifyPayloadLength(payloadLength); verifyPayloadLength(payloadLength);
int lengthWithoutPriority = flags.getNumPaddingLengthBytes(); int requiredLength = flags.getPaddingPresenceFieldLength() + flags.getNumPriorityBytes();
if (lengthWithoutPriority < 0) { if (payloadLength < requiredLength) {
throw protocolError("Frame length too small." + payloadLength); 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 { private void verifyPriorityFrame() throws Http2Exception {
@ -291,15 +282,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
verifyNotProcessingHeaders(); verifyNotProcessingHeaders();
verifyPayloadLength(payloadLength); 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 // Subtract the length of the promised stream ID field, to determine the length of the
// rest of the payload (header block fragment + payload). // rest of the payload (header block fragment + payload).
int lengthWithoutPromisedId = payloadLength - INT_FIELD_LENGTH; int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
if (lengthWithoutPromisedId < flags.getNumPaddingLengthBytes()) { if (payloadLength < minLength) {
throw protocolError("Frame length %d too small for padding.", payloadLength); 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); + "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
} }
if (!flags.isPaddingLengthValid()) { if (payloadLength < flags.getPaddingPresenceFieldLength()) {
throw protocolError("Pad high is set but pad low is not");
}
if (payloadLength < flags.getNumPaddingLengthBytes()) {
throw protocolError("Frame length %d too small for padding.", payloadLength); 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, private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload,
Http2FrameObserver observer) throws Http2Exception { 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 // Determine how much data there is to read by removing the trailing
// padding. // padding.
int dataLength = payload.readableBytes() - dataPadding; int dataLength = payload.readableBytes() - padding;
if (dataLength < 0) { if (dataLength < 0) {
throw protocolError("Frame payload too small for padding."); throw protocolError("Frame payload too small for padding.");
} }
ByteBuf data = payload.readSlice(dataLength); ByteBuf data = payload.readSlice(dataLength);
observer.onDataRead(ctx, streamId, data, dataPadding, flags.endOfStream(), observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream(),
flags.endOfSegment(), flags.compressed()); flags.endOfSegment());
payload.skipBytes(payload.readableBytes()); payload.skipBytes(payload.readableBytes());
} }
@ -396,7 +360,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
Http2FrameObserver observer) throws Http2Exception { Http2FrameObserver observer) throws Http2Exception {
final int headersStreamId = streamId; final int headersStreamId = streamId;
final Http2Flags headersFlags = flags; 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 // The callback that is invoked is different depending on whether priority information
// is present in the headers frame. // is present in the headers frame.
@ -415,7 +379,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
} }
@Override @Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding, public void processFragment(boolean endOfHeaders, ByteBuf fragment,
Http2FrameObserver observer) throws Http2Exception { Http2FrameObserver observer) throws Http2Exception {
builder().addFragment(fragment, ctx.alloc(), endOfHeaders); builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
if (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. // 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; return;
} }
@ -442,7 +406,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
} }
@Override @Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding, public void processFragment(boolean endOfHeaders, ByteBuf fragment,
Http2FrameObserver observer) throws Http2Exception { Http2FrameObserver observer) throws Http2Exception {
builder().addFragment(fragment, ctx.alloc(), endOfHeaders); builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
if (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. // Process the initial fragment, invoking the observer's callback if end of headers.
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding); 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, private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
@ -480,45 +444,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
observer.onSettingsAckRead(ctx); observer.onSettingsAckRead(ctx);
} else { } else {
int numSettings = payloadLength / SETTING_ENTRY_LENGTH; int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
Http2Settings settings = new Http2Settings(); Http2Settings settings = new Http2Settings(5);
for (int index = 0; index < numSettings; ++index) { for (int index = 0; index < numSettings; ++index) {
short id = payload.readUnsignedByte(); int id = payload.readUnsignedShort();
long value = payload.readUnsignedInt(); long value = payload.readUnsignedInt();
switch (id) { settings.put(id, value);
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);
}
} }
observer.onSettingsRead(ctx, settings); observer.onSettingsRead(ctx, settings);
} }
@ -527,7 +457,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload, private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
Http2FrameObserver observer) throws Http2Exception { Http2FrameObserver observer) throws Http2Exception {
final int pushPromiseStreamId = streamId; final int pushPromiseStreamId = streamId;
int padding = flags.readPaddingLength(payload); final int padding = readPadding(payload);
final int promisedStreamId = readUnsignedInt(payload); final int promisedStreamId = readUnsignedInt(payload);
// Create a handler that invokes the observer when the header block is complete. // Create a handler that invokes the observer when the header block is complete.
@ -538,7 +468,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
} }
@Override @Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding, public void processFragment(boolean endOfHeaders, ByteBuf fragment,
Http2FrameObserver observer) throws Http2Exception { Http2FrameObserver observer) throws Http2Exception {
builder().addFragment(fragment, ctx.alloc(), endOfHeaders); builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
if (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. // Process the initial fragment, invoking the observer's callback if end of headers.
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding); 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, private void readPingFrame(ChannelHandlerContext ctx, ByteBuf payload,
@ -581,30 +511,26 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
private void readContinuationFrame(ByteBuf payload, Http2FrameObserver observer) private void readContinuationFrame(ByteBuf payload, Http2FrameObserver observer)
throws Http2Exception { throws Http2Exception {
int padding = flags.readPaddingLength(payload);
// Process the initial fragment, invoking the observer's callback if end of headers. // Process the initial fragment, invoking the observer's callback if end of headers.
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes() - padding); final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes());
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment, padding, headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment,
observer); observer);
} }
private void readAltSvcFrame(ChannelHandlerContext ctx, ByteBuf payload, private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload, Http2FrameObserver observer)
Http2FrameObserver observer) throws Http2Exception { throws Http2Exception {
long maxAge = payload.readUnsignedInt(); payload = payload.readSlice(payload.readableBytes());
int port = payload.readUnsignedShort(); observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
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());
} }
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
/**
* 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;
}
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 endOfHeaders whether the fragment is the last in the header block.
* @param fragment the fragment of the header block to be added. * @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. * @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; Http2FrameObserver observer) throws Http2Exception;
final HeadersBuilder builder() { final HeadersBuilder builder() {

View File

@ -15,14 +15,34 @@
package io.netty.handler.codec.http2; 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.ByteBuf;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.util.collection.IntObjectMap;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static io.netty.util.CharsetUtil.*;
/** /**
* A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification. * A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification.
@ -40,12 +60,12 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
} }
@Override @Override
public void maxHeaderTableSize(int max) throws Http2Exception { public void maxHeaderTableSize(long max) throws Http2Exception {
headersEncoder.maxHeaderTableSize(max); headersEncoder.maxHeaderTableSize((int) Math.min(max, Integer.MAX_VALUE));
} }
@Override @Override
public int maxHeaderTableSize() { public long maxHeaderTableSize() {
return headersEncoder.maxHeaderTableSize(); return headersEncoder.maxHeaderTableSize();
} }
@ -56,21 +76,21 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
@Override @Override
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed) { ByteBuf data, int padding, boolean endStream, boolean endSegment) {
try { try {
verifyStreamId(streamId, "Stream ID"); verifyStreamId(streamId, "Stream ID");
verifyPadding(padding); verifyPadding(padding);
Http2Flags flags = Http2Flags flags =
Http2Flags.newBuilder().setPaddingFlags(padding).endOfStream(endStream) new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream)
.endOfSegment(endSegment).compressed(compressed).build(); .endOfSegment(endSegment);
int payloadLength = data.readableBytes() + padding + flags.getNumPaddingLengthBytes(); int payloadLength = data.readableBytes() + padding + flags.getPaddingPresenceFieldLength();
verifyPayloadLength(payloadLength); verifyPayloadLength(payloadLength);
ByteBuf out = ctx.alloc().buffer(FRAME_HEADER_LENGTH + 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); writePaddingLength(padding, out);
@ -111,9 +131,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
verifyWeight(weight); verifyWeight(weight);
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH); ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH);
writeFrameHeader(frame, PRIORITY_ENTRY_LENGTH, Http2FrameType.PRIORITY, writeFrameHeader(frame, PRIORITY_ENTRY_LENGTH, PRIORITY,
Http2Flags.EMPTY, streamId); new Http2Flags(), streamId);
long word1 = exclusive ? 0x80000000L | streamDependency : streamDependency; long word1 = exclusive ? (0x80000000L | streamDependency) : streamDependency;
writeUnsignedInt(word1, frame); writeUnsignedInt(word1, frame);
// Adjust the weight so that it fits into a single byte on the wire. // 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); verifyErrorCode(errorCode);
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH); 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); streamId);
writeUnsignedInt(errorCode, frame); writeUnsignedInt(errorCode, frame);
return ctx.writeAndFlush(frame, promise); return ctx.writeAndFlush(frame, promise);
@ -145,10 +165,16 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
public ChannelFuture writeSettings(ChannelHandlerContext ctx, ChannelPromise promise, public ChannelFuture writeSettings(ChannelHandlerContext ctx, ChannelPromise promise,
Http2Settings settings) { Http2Settings settings) {
try { 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); ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
writeFrameHeader(frame, payloadLength, Http2FrameType.SETTINGS, Http2Flags.EMPTY, 0); writeFrameHeader(frame, payloadLength, SETTINGS, new Http2Flags(), 0);
writeSettingsPayload(settings, frame); for (IntObjectMap.Entry<Long> entry : settings.entries()) {
writeUnsignedShort(entry.key(), frame);
writeUnsignedInt(entry.value(), frame);
}
return ctx.writeAndFlush(frame, promise); return ctx.writeAndFlush(frame, promise);
} catch (RuntimeException e) { } catch (RuntimeException e) {
return promise.setFailure(e); return promise.setFailure(e);
@ -159,7 +185,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) { public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
try { try {
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH); 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); return ctx.writeAndFlush(frame, promise);
} catch (RuntimeException e) { } catch (RuntimeException e) {
return promise.setFailure(e); return promise.setFailure(e);
@ -171,8 +197,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
ByteBuf data) { ByteBuf data) {
try { try {
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + data.readableBytes()); ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + data.readableBytes());
Http2Flags flags = ack ? Http2Flags.ACK_ONLY : Http2Flags.EMPTY; Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
writeFrameHeader(frame, data.readableBytes(), Http2FrameType.PING, flags, 0); writeFrameHeader(frame, data.readableBytes(), PING, flags, 0);
// Write the debug data. // Write the debug data.
frame.writeBytes(data, data.readerIndex(), data.readableBytes()); frame.writeBytes(data, data.readerIndex(), data.readableBytes());
@ -198,21 +224,18 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
headersEncoder.encodeHeaders(headers, headerBlock); headersEncoder.encodeHeaders(headers, headerBlock);
// Read the first fragment (possibly everything). // 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 promisedStreamIdLength = INT_FIELD_LENGTH;
int maxFragmentLength = int nonFragmentLength = promisedStreamIdLength + padding + flags.getPaddingPresenceFieldLength();
MAX_FRAME_PAYLOAD_LENGTH int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength;
- (promisedStreamIdLength + padding + flags.getNumPaddingLengthBytes());
ByteBuf fragment = ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0); flags.endOfHeaders(headerBlock.readableBytes() == 0);
int payloadLength = int payloadLength = fragment.readableBytes() + nonFragmentLength;
fragment.readableBytes() + promisedStreamIdLength + padding
+ flags.getNumPaddingLengthBytes();
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength); ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
writeFrameHeader(firstFrame, payloadLength, Http2FrameType.PUSH_PROMISE, flags.build(), writeFrameHeader(firstFrame, payloadLength, PUSH_PROMISE, flags,
streamId); streamId);
writePaddingLength(padding, firstFrame); writePaddingLength(padding, firstFrame);
@ -250,7 +273,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
int payloadLength = 8 + debugData.readableBytes(); int payloadLength = 8 + debugData.readableBytes();
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength); 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); frame.writeInt(lastStreamId);
writeUnsignedInt(errorCode, frame); writeUnsignedInt(errorCode, frame);
frame.writeBytes(debugData, debugData.readerIndex(), debugData.readableBytes()); frame.writeBytes(debugData, debugData.readerIndex(), debugData.readableBytes());
@ -270,8 +293,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
verifyWindowSizeIncrement(windowSizeIncrement); verifyWindowSizeIncrement(windowSizeIncrement);
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH); ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH);
writeFrameHeader(frame, INT_FIELD_LENGTH, Http2FrameType.WINDOW_UPDATE, writeFrameHeader(frame, INT_FIELD_LENGTH, WINDOW_UPDATE,
Http2Flags.EMPTY, streamId); new Http2Flags(), streamId);
frame.writeInt(windowSizeIncrement); frame.writeInt(windowSizeIncrement);
return ctx.writeAndFlush(frame, promise); return ctx.writeAndFlush(frame, promise);
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -280,49 +303,13 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
} }
@Override @Override
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise, public ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise,
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) { byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
try { try {
verifyStreamOrConnectionId(streamId, "Stream ID"); verifyStreamOrConnectionId(streamId, "Stream ID");
verifyMaxAge(maxAge); ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
verifyPort(port); writeFrameHeader(frame, payload.readableBytes(), frameType, flags, streamId);
frame.writeBytes(payload);
// 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);
return ctx.writeAndFlush(frame, promise); return ctx.writeAndFlush(frame, promise);
} catch (RuntimeException e) { } catch (RuntimeException e) {
return promise.setFailure(e); return promise.setFailure(e);
@ -345,23 +332,23 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
headerBlock = ctx.alloc().buffer(); headerBlock = ctx.alloc().buffer();
headersEncoder.encodeHeaders(headers, headerBlock); headersEncoder.encodeHeaders(headers, headerBlock);
Http2Flags.Builder flags = Http2Flags flags =
Http2Flags.newBuilder().endOfStream(endStream).endOfSegment(endSegment) new Http2Flags().endOfStream(endStream).endOfSegment(endSegment)
.priorityPresent(hasPriority).setPaddingFlags(padding); .priorityPresent(hasPriority).paddingPresent(padding > 0);
// Read the first fragment (possibly everything). // Read the first fragment (possibly everything).
int nonFragmentBytes = int nonFragmentBytes =
padding + flags.getNumPriorityBytes() + flags.getNumPaddingLengthBytes(); padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength();
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentBytes; int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentBytes;
ByteBuf fragment = ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
// Set the end of headers flag for the first frame. // 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; int payloadLength = fragment.readableBytes() + nonFragmentBytes;
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength); ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
writeFrameHeader(firstFrame, payloadLength, Http2FrameType.HEADERS, flags.build(), writeFrameHeader(firstFrame, payloadLength, HEADERS, flags,
streamId); streamId);
// Write the padding length. // Write the padding length.
@ -425,17 +412,17 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
*/ */
private static ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId, private static ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId,
ByteBuf headerBlock, int padding) { ByteBuf headerBlock, int padding) {
Http2Flags.Builder flags = Http2Flags.newBuilder().setPaddingFlags(padding); Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
int maxFragmentLength = int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength();
MAX_FRAME_PAYLOAD_LENGTH - (padding + flags.getNumPaddingLengthBytes()); int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength;
ByteBuf fragment = ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)); 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); ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0); flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
writeFrameHeader(frame, payloadLength, Http2FrameType.CONTINUATION, flags.build(), streamId); writeFrameHeader(frame, payloadLength, CONTINUATION, flags, streamId);
writePaddingLength(padding, frame); writePaddingLength(padding, frame);
@ -474,7 +461,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
} }
private static void verifyPadding(int padding) { 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); throw new IllegalArgumentException("Invalid padding value: " + padding);
} }
} }
@ -503,16 +490,4 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
throw new IllegalArgumentException("WindowSizeIncrement must be >= 0"); 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);
}
}
} }

View File

@ -16,7 +16,7 @@
package io.netty.handler.codec.http2; 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.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.flowControlError;
import static io.netty.handler.codec.http2.Http2Exception.protocolError; import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -27,7 +27,7 @@ import io.netty.buffer.ByteBuf;
public class DefaultHttp2InboundFlowController implements Http2InboundFlowController { public class DefaultHttp2InboundFlowController implements Http2InboundFlowController {
private final Http2Connection connection; private final Http2Connection connection;
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE; private int initialWindowSize = DEFAULT_WINDOW_SIZE;
public DefaultHttp2InboundFlowController(Http2Connection connection) { public DefaultHttp2InboundFlowController(Http2Connection connection) {
if (connection == null) { if (connection == null) {
@ -66,7 +66,7 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro
@Override @Override
public void applyInboundFlowControl(int streamId, ByteBuf data, int padding, public void applyInboundFlowControl(int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed, FrameWriter frameWriter) boolean endOfStream, boolean endOfSegment, FrameWriter frameWriter)
throws Http2Exception { throws Http2Exception {
int dataLength = data.readableBytes(); int dataLength = data.readableBytes();
applyConnectionFlowControl(dataLength, frameWriter); applyConnectionFlowControl(dataLength, frameWriter);

View File

@ -57,7 +57,7 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
}; };
private final Http2Connection connection; private final Http2Connection connection;
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE; private int initialWindowSize = DEFAULT_WINDOW_SIZE;
public DefaultHttp2OutboundFlowController(Http2Connection connection) { public DefaultHttp2OutboundFlowController(Http2Connection connection) {
if (connection == null) { 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 @Override
public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream, public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream,
boolean endSegment, boolean compressed, FrameWriter frameWriter) throws Http2Exception { boolean endSegment, boolean compressed, FrameWriter frameWriter) throws Http2Exception {

View File

@ -38,12 +38,6 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
this.observer = observer; this.observer = observer;
} }
public DelegatingHttp2ConnectionHandler(boolean server, boolean allowCompression,
Http2FrameObserver observer) {
super(server, allowCompression);
this.observer = observer;
}
public DelegatingHttp2ConnectionHandler(Http2Connection connection, public DelegatingHttp2ConnectionHandler(Http2Connection connection,
Http2FrameReader frameReader, Http2FrameWriter frameWriter, Http2FrameReader frameReader, Http2FrameWriter frameWriter,
Http2InboundFlowController inboundFlow, Http2OutboundFlowController outboundFlow, Http2InboundFlowController inboundFlow, Http2OutboundFlowController outboundFlow,
@ -107,16 +101,10 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
return super.writePushPromise(ctx, promise, streamId, promisedStreamId, headers, padding); 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 @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception { boolean endOfStream, boolean endOfSegment) throws Http2Exception {
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment, compressed); observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment);
} }
@Override @Override
@ -178,13 +166,8 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port, public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf protocolId, String host, String origin) throws Http2Exception { ByteBuf payload) {
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin); observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
}
@Override
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
observer.onBlockedRead(ctx, streamId);
} }
} }

View File

@ -20,6 +20,7 @@ import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientUpgradeHandler; import io.netty.handler.codec.http.HttpClientUpgradeHandler;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.collection.IntObjectMap;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -103,8 +104,12 @@ public class Http2ClientUpgradeCodec implements HttpClientUpgradeHandler.Upgrade
Http2Settings settings = connectionHandler.settings(); Http2Settings settings = connectionHandler.settings();
// Serialize the payload of the SETTINGS frame. // Serialize the payload of the SETTINGS frame.
buf = ctx.alloc().buffer(calcSettingsPayloadLength(settings)); int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
writeSettingsPayload(settings, buf); 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. // Base64 encode the payload and then convert to a string for the header.
encodedBuf = Base64.encode(buf, URL_SAFE); encodedBuf = Base64.encode(buf, URL_SAFE);

View File

@ -15,16 +15,15 @@
package io.netty.handler.codec.http2; 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.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action; import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action;
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. * 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 CONNECTION_STREAM_ID = 0;
public static final int HTTP_UPGRADE_STREAM_ID = 1; public static final int HTTP_UPGRADE_STREAM_ID = 1;
public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings"; public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings";
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-12"; public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-13";
public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383; public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383;
public static final int PING_FRAME_PAYLOAD_LENGTH = 8; public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
@ -45,19 +44,19 @@ public final class Http2CodecUtil {
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL; public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
public static final int FRAME_HEADER_LENGTH = 8; public static final int FRAME_HEADER_LENGTH = 8;
public static final int FRAME_LENGTH_MASK = 0x3FFF; 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 PRIORITY_ENTRY_LENGTH = 5;
public static final int INT_FIELD_LENGTH = 4; public static final int INT_FIELD_LENGTH = 4;
public static final short MAX_WEIGHT = 256; public static final short MAX_WEIGHT = (short) 256;
public static final short MIN_WEIGHT = 1; public static final short MIN_WEIGHT = (short) 1;
public static final short SETTINGS_HEADER_TABLE_SIZE = 1; public static final int SETTINGS_HEADER_TABLE_SIZE = 1;
public static final short SETTINGS_ENABLE_PUSH = 2; public static final int SETTINGS_ENABLE_PUSH = 2;
public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3; public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4; public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4;
public static final short SETTINGS_COMPRESS_DATA = 5;
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 short DEFAULT_PRIORITY_WEIGHT = 16;
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096; public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
public static final int DEFAULT_MAX_HEADER_SIZE = 8192; public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
@ -163,58 +162,15 @@ public final class Http2CodecUtil {
/** /**
* Writes an HTTP/2 frame header to the output buffer. * 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) { Http2Flags flags, int streamId) {
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength); out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength); out.writeShort(payloadLength);
out.writeByte(type.typeCode()); out.writeByte(type);
out.writeByte(flags.value()); out.writeByte(flags.value());
out.writeInt(streamId); 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. * Fails the given promise with the cause and then re-throws the cause.
*/ */

View File

@ -177,16 +177,6 @@ public interface Http2Connection {
*/ */
void maxStreams(int maxStreams); 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. * Gets the ID of the stream last successfully created by this endpoint.
*/ */

View File

@ -19,19 +19,19 @@ package io.netty.handler.codec.http2;
* All error codes identified by the HTTP/2 spec. * All error codes identified by the HTTP/2 spec.
*/ */
public enum Http2Error { public enum Http2Error {
NO_ERROR(0), NO_ERROR(0x0),
PROTOCOL_ERROR(1), PROTOCOL_ERROR(0x1),
INTERNAL_ERROR(2), INTERNAL_ERROR(0x2),
FLOW_CONTROL_ERROR(3), FLOW_CONTROL_ERROR(0x3),
SETTINGS_TIMEOUT(4), SETTINGS_TIMEOUT(0x4),
STREAM_CLOSED(5), STREAM_CLOSED(0x5),
FRAME_SIZE_ERROR(6), FRAME_SIZE_ERROR(0x6),
REFUSED_STREAM(7), REFUSED_STREAM(0x7),
CANCEL(8), CANCEL(0x8),
COMPRESSION_ERROR(9), COMPRESSION_ERROR(0x9),
CONNECT_ERROR(10), CONNECT_ERROR(0xA),
ENHANCE_YOUR_CALM(11), ENHANCE_YOUR_CALM(0xB),
INADEQUATE_SECURITY(12); INADEQUATE_SECURITY(0xC);
private final int code; private final int code;

View File

@ -15,28 +15,20 @@
package io.netty.handler.codec.http2; 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. * Provides utility methods for accessing specific flags as defined by the HTTP/2 spec.
*/ */
public class Http2Flags { public final class Http2Flags {
public static final Http2Flags EMPTY = new Http2Flags();
public static final Http2Flags ACK_ONLY = new Builder().ack(true).build();
public static final short END_STREAM = 0x1; public static final short END_STREAM = 0x1;
public static final short END_SEGMENT = 0x2; public static final short END_SEGMENT = 0x2;
public static final short END_HEADERS = 0x4; public static final short END_HEADERS = 0x4;
public static final short ACK = 0x1; public static final short ACK = 0x1;
public static final short PAD_LOW = 0x8; public static final short PADDED = 0x8;
public static final short PAD_HIGH = 0x10;
public static final short PRIORITY = 0x20; public static final short PRIORITY = 0x20;
public static final short COMPRESSED = 0x20;
private final short value; private short value;
private Http2Flags() { public Http2Flags() {
this((short) 0);
} }
public Http2Flags(short value) { public Http2Flags(short value) {
@ -55,7 +47,7 @@ public class Http2Flags {
* frames. * frames.
*/ */
public boolean endOfStream() { public boolean endOfStream() {
return endOfStream(value); return isFlagSet(END_STREAM);
} }
/** /**
@ -63,7 +55,7 @@ public class Http2Flags {
* frames. * frames.
*/ */
public boolean endOfSegment() { public boolean endOfSegment() {
return endOfSegment(value); return isFlagSet(END_SEGMENT);
} }
/** /**
@ -71,7 +63,7 @@ public class Http2Flags {
* PUSH_PROMISE, and CONTINUATION frames. * PUSH_PROMISE, and CONTINUATION frames.
*/ */
public boolean endOfHeaders() { 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. * dependency, and weight fields in a HEADERS frame.
*/ */
public boolean priorityPresent() { public boolean priorityPresent() {
return priorityPresent(value); return isFlagSet(PRIORITY);
} }
/** /**
@ -87,46 +79,15 @@ public class Http2Flags {
* SETTINGS and PING frames. * SETTINGS and PING frames.
*/ */
public boolean ack() { public boolean ack() {
return ack(value); return isFlagSet(ACK);
} }
/** /**
* For DATA frames, indicates that the data is compressed using gzip compression. * For frames that include padding, indicates if the {@link #PADDED} field is present. Only
*/
public boolean compressed() {
return 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. * applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
*/ */
public boolean padLowPresent() { public boolean paddingPresent() {
return padLowPresent(value); return isFlagSet(PADDED);
}
/**
* 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);
} }
/** /**
@ -134,14 +95,81 @@ public class Http2Flags {
* by the {@link #priorityPresent()} flag. * by the {@link #priorityPresent()} flag.
*/ */
public int getNumPriorityBytes() { 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) { public int getPaddingPresenceFieldLength() {
return readPaddingLength(value, payload); 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 @Override
@ -186,298 +214,10 @@ public class Http2Flags {
if (endOfSegment()) { if (endOfSegment()) {
builder.append("END_OF_SEGMENT,"); builder.append("END_OF_SEGMENT,");
} }
if (padHighPresent()) { if (paddingPresent()) {
builder.append("PAD_HIGH,"); builder.append("PADDING_PRESENT,");
}
if (padLowPresent()) {
builder.append("PAD_LOW,");
} }
builder.append(')'); builder.append(')');
return builder.toString(); 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;
}
} }

View File

@ -24,7 +24,7 @@ public class Http2FrameAdapter implements Http2FrameObserver {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception { boolean endOfStream, boolean endOfSegment) throws Http2Exception {
} }
@Override @Override
@ -81,11 +81,7 @@ public class Http2FrameAdapter implements Http2FrameObserver {
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port, public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf protocolId, String host, String origin) throws Http2Exception { ByteBuf payload) {
}
@Override
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
} }
} }

View File

@ -16,6 +16,7 @@
package io.netty.handler.codec.http2; package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerAdapter;
import io.netty.util.internal.logging.InternalLogLevel; import io.netty.util.internal.logging.InternalLogLevel;
import io.netty.util.internal.logging.InternalLogger; 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, public void logData(Direction direction, int streamId, ByteBuf data, int padding,
boolean endStream, boolean endSegment, boolean compressed) { boolean endStream, boolean endSegment) {
log(direction, log(direction,
"DATA: streamId=%d, dataLen=%d, padding=%d, endStream=%b, endSegment=%b, compressed=%b", "DATA: streamId=%d, padding=%d, endStream=%b, endSegment=%b, length=%d, bytes=%s",
streamId, data.readableBytes(), padding, endStream, endSegment, compressed); streamId, padding, endStream, endSegment, data.readableBytes(), ByteBufUtil.hexDump(data));
} }
public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding, public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding,
@ -90,11 +91,11 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
} }
public void logPing(Direction direction, ByteBuf data) { 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) { 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, 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) { public void logGoAway(Direction direction, int lastStreamId, long errorCode, ByteBuf debugData) {
log(direction, "GO_AWAY: lastStreamId=%d, errorCode=%d, dataLen=%d", lastStreamId, log(direction, "GO_AWAY: lastStreamId=%d, errorCode=%d, length=%d, bytes=%s", lastStreamId,
errorCode, debugData.readableBytes()); errorCode, debugData.readableBytes(), ByteBufUtil.hexDump(debugData));
} }
public void logWindowsUpdate(Direction direction, int streamId, int windowSizeIncrement) { public void logWindowsUpdate(Direction direction, int streamId, int windowSizeIncrement) {
@ -113,15 +114,9 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
windowSizeIncrement); windowSizeIncrement);
} }
public void logAltSvc(Direction direction, int streamId, long maxAge, int port, public void logUnknownFrame(Direction direction, byte frameType, int streamId, Http2Flags flags, ByteBuf data) {
ByteBuf protocolId, String host, String origin) { log(direction, "UNKNOWN: frameType=%d, streamId=%d, flags=%d, length=%d, bytes=%s",
log(direction, frameType & 0xFF, streamId, flags.value(), data.readableBytes(), ByteBufUtil.hexDump(data));
"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);
} }
private void log(Direction direction, String format, Object... args) { private void log(Direction direction, String format, Object... args) {

View File

@ -34,10 +34,9 @@ public interface Http2FrameObserver {
* @param endOfStream Indicates whether this is the last frame to be sent from the remote * @param endOfStream Indicates whether this is the last frame to be sent from the remote
* endpoint for this stream. * endpoint for this stream.
* @param endOfSegment Indicates whether this frame is the end of the current segment. * @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, 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. * Handles an inbound HEADERS frame.
@ -162,26 +161,13 @@ public interface Http2FrameObserver {
throws Http2Exception; 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 ctx the context from the handler where the frame was read.
* @param streamId the stream. * @param frameType the frame type from the HTTP/2 header.
* @param maxAge the freshness lifetime of the alternative service association. * @param streamId the stream the frame was sent on.
* @param port the port that the alternative service is available upon. * @param flags the flags in the frame header.
* @param protocolId the ALPN protocol identifier of the alternative service. If this buffer * @param payload the payload of the frame.
* 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}.
*/ */
void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port, void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload);
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;
} }

View File

@ -36,12 +36,12 @@ public interface Http2FrameReader extends Closeable {
/** /**
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers. * 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. * 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. * Closes this reader and frees any allocated resources.

View File

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

View File

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

View File

@ -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 padding the amount of padding to be added to the end of the frame
* @param endStream indicates if this is the last frame to be sent for the stream. * @param endStream indicates if this is the last frame to be sent for the stream.
* @param endSegment indicates if this is the last frame in the current segment. * @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. * @return the future for the write.
*/ */
ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed); ByteBuf data, int padding, boolean endStream, boolean endSegment);
/** /**
* Writes a HEADERS frame to the remote endpoint. * Writes a HEADERS frame to the remote endpoint.
@ -179,31 +178,18 @@ public interface Http2FrameWriter extends Closeable {
int streamId, int windowSizeIncrement); 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 ctx the context to use for writing.
* @param promise the promise for the write. * @param promise the promise for the write.
* @param streamId the stream. * @param frameType the frame type identifier.
* @param maxAge the freshness lifetime of the alternative service association. * @param streamId the stream for which to send the frame.
* @param port the port that the alternative service is available upon. * @param flags the flags to write for this frame.
* @param protocolId the ALPN protocol identifier of the alternative service. * @param payload the payload to write for this frame.
* @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}.
* @return the future for the write. * @return the future for the write.
*/ */
ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise, byte frameType,
long maxAge, int port, ByteBuf protocolId, String host, String origin); int streamId, Http2Flags flags, ByteBuf payload);
/**
* 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);
/** /**
* Closes this writer and frees any allocated resources. * 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. * 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. * Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
*/ */
int maxHeaderTableSize(); long maxHeaderTableSize();
} }

View File

@ -59,6 +59,5 @@ public interface Http2InboundFlowController {
* @throws Http2Exception thrown if any protocol-related error occurred. * @throws Http2Exception thrown if any protocol-related error occurred.
*/ */
void applyInboundFlowControl(int streamId, ByteBuf data, int padding, boolean endOfStream, void applyInboundFlowControl(int streamId, ByteBuf data, int padding, boolean endOfStream,
boolean endOfSegment, boolean compressed, FrameWriter frameWriter) boolean endOfSegment, FrameWriter frameWriter) throws Http2Exception;
throws Http2Exception;
} }

View File

@ -46,11 +46,10 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, boolean endOfSegment, boolean compressed) int padding, boolean endOfStream, boolean endOfSegment)
throws Http2Exception { throws Http2Exception {
logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment, logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment);
compressed); observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment);
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment, compressed);
} }
@Override @Override
@ -132,16 +131,10 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
int port, ByteBuf protocolId, String host, String origin) throws Http2Exception { Http2Flags flags, ByteBuf payload) {
logger.logAltSvc(INBOUND, streamId, maxAge, port, protocolId, host, origin); logger.logUnknownFrame(INBOUND, frameType, streamId, flags, payload);
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin); observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
}
@Override
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
logger.logBlocked(INBOUND, streamId);
observer.onBlockedRead(ctx, streamId);
} }
}); });
} }
@ -152,12 +145,12 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
} }
@Override @Override
public void maxHeaderTableSize(int max) { public void maxHeaderTableSize(long max) {
reader.maxHeaderTableSize(max); reader.maxHeaderTableSize(max);
} }
@Override @Override
public int maxHeaderTableSize() { public long maxHeaderTableSize() {
return reader.maxHeaderTableSize(); return reader.maxHeaderTableSize();
} }

View File

@ -40,7 +40,7 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
public enum SelectedProtocol { public enum SelectedProtocol {
/** Must be updated to match the HTTP/2 draft number. */ /** Must be updated to match the HTTP/2 draft number. */
HTTP_2("h2-12"), HTTP_2("h2-13"),
HTTP_1_1("http/1.1"), HTTP_1_1("http/1.1"),
HTTP_1_0("http/1.0"), HTTP_1_0("http/1.0"),
UNKNOWN("Unknown"); UNKNOWN("Unknown");

View File

@ -64,15 +64,6 @@ public interface Http2OutboundFlowController {
*/ */
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception; 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, * 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. * depending on whether the remote endpoint can receive the frame now.

View File

@ -43,10 +43,9 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
@Override @Override
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId, public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed) { ByteBuf data, int padding, boolean endStream, boolean endSegment) {
logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment, compressed); logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment);
return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment, return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment);
compressed);
} }
@Override @Override
@ -121,17 +120,10 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
} }
@Override @Override
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise, public ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise,
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) { byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
logger.logAltSvc(OUTBOUND, streamId, maxAge, port, protocolId, host, origin); logger.logUnknownFrame(OUTBOUND, frameType, streamId, flags, payload);
return writer.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host, origin); return writer.writeFrame(ctx, promise, frameType, streamId, flags, payload);
}
@Override
public ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise,
int streamId) {
logger.logBlocked(OUTBOUND, streamId);
return writer.writeBlocked(ctx, promise, streamId);
} }
@Override @Override
@ -140,12 +132,12 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
} }
@Override @Override
public void maxHeaderTableSize(int max) throws Http2Exception { public void maxHeaderTableSize(long max) throws Http2Exception {
writer.maxHeaderTableSize(max); writer.maxHeaderTableSize(max);
} }
@Override @Override
public int maxHeaderTableSize() { public long maxHeaderTableSize() {
return writer.maxHeaderTableSize(); return writer.maxHeaderTableSize();
} }
} }

View File

@ -14,6 +14,13 @@
*/ */
package io.netty.handler.codec.http2; 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.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -27,12 +34,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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. * 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(); final Http2Settings decodedSettings = new Http2Settings();
frameReader.readFrame(ctx, frame, new Http2FrameAdapter() { frameReader.readFrame(ctx, frame, new Http2FrameAdapter() {
@Override @Override
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) { public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)
decodedSettings.copy(settings); throws Http2Exception {
decodedSettings.copyFrom(settings);
} }
}); });
return decodedSettings; return decodedSettings;
@ -153,7 +155,7 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
*/ */
private static ByteBuf createSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload) { private static ByteBuf createSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload) {
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes()); 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); frame.writeBytes(payload);
payload.release(); payload.release();
return frame; return frame;

View File

@ -15,258 +15,112 @@
package io.netty.handler.codec.http2; 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 * 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 { public final class Http2Settings extends IntObjectHashMap<Long> {
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;
private byte enabled; public Http2Settings() {
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(int initialCapacity, float loadFactor) {
* Gets the maximum HPACK header table size or throws {@link NoSuchElementException} if the super(initialCapacity, loadFactor);
* value has not been set.
*/
public int maxHeaderTableSize() {
if (!hasMaxHeaderTableSize()) {
throw new NoSuchElementException("headerTableSize");
}
return maxHeaderTableSize;
} }
/** public Http2Settings(int initialCapacity) {
* Sets the maximum HPACK header table size to the specified value. super(initialCapacity);
*/
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;
} }
@Override @Override
public int hashCode() { public Long put(int key, Long value) {
final int prime = 31; verifyStandardSetting(key, value);
int result = 1; return super.put(key, value);
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;
} }
@Override public Long headerTableSize() {
public boolean equals(Object obj) { return get(SETTINGS_HEADER_TABLE_SIZE);
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 Http2Settings headerTableSize(long value) {
put(SETTINGS_HEADER_TABLE_SIZE, value);
return this;
} }
@Override public Boolean pushEnabled() {
public String toString() { Long value = get(SETTINGS_ENABLE_PUSH);
StringBuilder builder = new StringBuilder("Http2Settings ["); if (value == null) {
if (hasMaxHeaderTableSize()) { return null;
builder.append("maxHeaderTableSize=").append(maxHeaderTableSize).append(',');
} }
if (hasPushEnabled()) { return value != 0L;
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();
} }
private void enable(int mask) { public Http2Settings pushEnabled(boolean enabled) {
enabled |= mask; put(SETTINGS_ENABLE_PUSH, enabled? 1L : 0L);
return this;
} }
private boolean isEnabled(int mask) { public Long maxConcurrentStreams() {
return (enabled & mask) > 0; 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;
}
} }
} }

View File

@ -38,8 +38,8 @@ public class DefaultHttp2ConnectionTest {
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
server = new DefaultHttp2Connection(true, false); server = new DefaultHttp2Connection(true);
client = new DefaultHttp2Connection(false, false); client = new DefaultHttp2Connection(false);
} }
@Test(expected = Http2Exception.class) @Test(expected = Http2Exception.class)

View File

@ -15,6 +15,10 @@
package io.netty.handler.codec.http2; package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.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.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@ -23,15 +27,13 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; 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}. * Integration tests for {@link DefaultHttp2FrameReader} and {@link DefaultHttp2FrameWriter}.
*/ */
@ -65,33 +67,33 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void emptyDataShouldRoundtrip() throws Exception { public void emptyDataShouldRoundtrip() throws Exception {
ByteBuf data = Unpooled.EMPTY_BUFFER; ByteBuf data = Unpooled.EMPTY_BUFFER;
writer.writeData(ctx, promise, 1000, data, 0, false, false, false); writer.writeData(ctx, promise, 1000, data, 0, false, false);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false)); verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false));
frame.release(); frame.release();
} }
@Test @Test
public void dataShouldRoundtrip() throws Exception { public void dataShouldRoundtrip() throws Exception {
ByteBuf data = dummyData(); ByteBuf data = dummyData();
writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false, false); writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false)); verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false));
frame.release(); frame.release();
} }
@Test @Test
public void dataWithPaddingShouldRoundtrip() throws Exception { public void dataWithPaddingShouldRoundtrip() throws Exception {
ByteBuf data = dummyData(); ByteBuf data = dummyData();
writer.writeData(ctx, promise, 1, data.retain().duplicate(), 256, true, true, true); writer.writeData(ctx, promise, 1, data.retain().duplicate(), 0xFF, true, true);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(256), eq(true), eq(true), eq(true)); verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(0xFF), eq(true), eq(true));
frame.release(); frame.release();
} }
@ -129,10 +131,9 @@ public class DefaultHttp2FrameIOTest {
public void settingsShouldStripShouldRoundtrip() throws Exception { public void settingsShouldStripShouldRoundtrip() throws Exception {
Http2Settings settings = new Http2Settings(); Http2Settings settings = new Http2Settings();
settings.pushEnabled(true); settings.pushEnabled(true);
settings.maxHeaderTableSize(4096); settings.headerTableSize(4096);
settings.initialWindowSize(123); settings.initialWindowSize(123);
settings.maxConcurrentStreams(456); settings.maxConcurrentStreams(456);
settings.allowCompressedData(false);
writer.writeSettings(ctx, promise, settings); writer.writeSettings(ctx, promise, settings);
@ -193,35 +194,6 @@ public class DefaultHttp2FrameIOTest {
frame.release(); 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 @Test
public void emptyHeadersShouldRoundtrip() throws Exception { public void emptyHeadersShouldRoundtrip() throws Exception {
Http2Headers headers = Http2Headers.EMPTY_HEADERS; Http2Headers headers = Http2Headers.EMPTY_HEADERS;
@ -235,10 +207,10 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception { public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception {
Http2Headers headers = Http2Headers.EMPTY_HEADERS; Http2Headers headers = Http2Headers.EMPTY_HEADERS;
writer.writeHeaders(ctx, promise, 1, headers, 256, true, true); writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true)); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true));
frame.release(); frame.release();
} }
@ -255,10 +227,10 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception { public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception {
Http2Headers headers = dummyHeaders(); Http2Headers headers = dummyHeaders();
writer.writeHeaders(ctx, promise, 1, headers, 256, true, true); writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true)); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true));
frame.release(); frame.release();
} }
@ -276,10 +248,10 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception { public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception {
Http2Headers headers = dummyHeaders(); Http2Headers headers = dummyHeaders();
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 256, true, true); writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256), verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF),
eq(true), eq(true)); eq(true), eq(true));
frame.release(); frame.release();
} }
@ -298,10 +270,10 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception { public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception {
Http2Headers headers = largeHeaders(); Http2Headers headers = largeHeaders();
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 256, true, true); writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); reader.readFrame(ctx, frame, observer);
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256), verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF),
eq(true), eq(true)); eq(true), eq(true));
frame.release(); frame.release();
} }
@ -329,10 +301,10 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void pushPromiseWithPaddingShouldRoundtrip() throws Exception { public void pushPromiseWithPaddingShouldRoundtrip() throws Exception {
Http2Headers headers = dummyHeaders(); Http2Headers headers = dummyHeaders();
writer.writePushPromise(ctx, promise, 1, 2, headers, 256); writer.writePushPromise(ctx, promise, 1, 2, headers, 0xFF);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }
@ -349,10 +321,10 @@ public class DefaultHttp2FrameIOTest {
@Test @Test
public void continuedPushPromiseWithPaddingShouldRoundtrip() throws Exception { public void continuedPushPromiseWithPaddingShouldRoundtrip() throws Exception {
Http2Headers headers = largeHeaders(); Http2Headers headers = largeHeaders();
writer.writePushPromise(ctx, promise, 1, 2, headers, 256); writer.writePushPromise(ctx, promise, 1, 2, headers, 0xFF);
ByteBuf frame = captureWrite(); ByteBuf frame = captureWrite();
reader.readFrame(ctx, frame, observer); 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(); frame.release();
} }

View File

@ -15,17 +15,22 @@
package io.netty.handler.codec.http2; 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.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http2.Http2InboundFlowController.FrameWriter; import io.netty.handler.codec.http2.Http2InboundFlowController.FrameWriter;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static org.mockito.Mockito.*;
/** /**
* Tests for {@link DefaultHttp2InboundFlowController}. * Tests for {@link DefaultHttp2InboundFlowController}.
*/ */
@ -46,7 +51,7 @@ public class DefaultHttp2InboundFlowControllerTest {
public void setup() throws Http2Exception { public void setup() throws Http2Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
connection = new DefaultHttp2Connection(false, false); connection = new DefaultHttp2Connection(false);
controller = new DefaultHttp2InboundFlowController(connection); controller = new DefaultHttp2InboundFlowController(connection);
connection.local().createStream(STREAM_ID, false); connection.local().createStream(STREAM_ID, false);
@ -60,14 +65,14 @@ public class DefaultHttp2InboundFlowControllerTest {
@Test(expected = Http2Exception.class) @Test(expected = Http2Exception.class)
public void connectionFlowControlExceededShouldThrow() throws Http2Exception { public void connectionFlowControlExceededShouldThrow() throws Http2Exception {
applyFlowControl(DEFAULT_FLOW_CONTROL_WINDOW_SIZE + 1, true); applyFlowControl(DEFAULT_WINDOW_SIZE + 1, true);
} }
@Test @Test
public void halfWindowRemainingShouldUpdateConnectionWindow() throws Http2Exception { public void halfWindowRemainingShouldUpdateConnectionWindow() throws Http2Exception {
int dataSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2 + 1; int dataSize = DEFAULT_WINDOW_SIZE / 2 + 1;
int newWindow = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - dataSize; int newWindow = DEFAULT_WINDOW_SIZE - dataSize;
int windowDelta = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - newWindow; int windowDelta = DEFAULT_WINDOW_SIZE - newWindow;
// Set end-of-stream on the frame, so no window update will be sent for the stream. // Set end-of-stream on the frame, so no window update will be sent for the stream.
applyFlowControl(dataSize, true); applyFlowControl(dataSize, true);
@ -76,8 +81,8 @@ public class DefaultHttp2InboundFlowControllerTest {
@Test @Test
public void halfWindowRemainingShouldUpdateAllWindows() throws Http2Exception { public void halfWindowRemainingShouldUpdateAllWindows() throws Http2Exception {
int dataSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2 + 1; int dataSize = DEFAULT_WINDOW_SIZE / 2 + 1;
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE; int initialWindowSize = DEFAULT_WINDOW_SIZE;
int windowDelta = getWindowDelta(initialWindowSize, initialWindowSize, dataSize); int windowDelta = getWindowDelta(initialWindowSize, initialWindowSize, dataSize);
// Don't set end-of-stream so we'll get a window update for the stream as well. // 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 @Test
public void initialWindowUpdateShouldAllowMoreFrames() throws Http2Exception { public void initialWindowUpdateShouldAllowMoreFrames() throws Http2Exception {
// Send a frame that takes up the entire window. // Send a frame that takes up the entire window.
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE; int initialWindowSize = DEFAULT_WINDOW_SIZE;
applyFlowControl(initialWindowSize, false); applyFlowControl(initialWindowSize, false);
// Update the initial window size to allow another frame. // 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 { private void applyFlowControl(int dataSize, boolean endOfStream) throws Http2Exception {
ByteBuf buf = dummyData(dataSize); ByteBuf buf = dummyData(dataSize);
controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false, controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false, frameWriter);
false, frameWriter);
buf.release(); buf.release();
} }

View File

@ -51,7 +51,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void setup() throws Http2Exception { public void setup() throws Http2Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
connection = new DefaultHttp2Connection(false, false); connection = new DefaultHttp2Connection(false);
controller = new DefaultHttp2OutboundFlowController(connection); controller = new DefaultHttp2OutboundFlowController(connection);
connection.local().createStream(STREAM_A, false); connection.local().createStream(STREAM_A, false);
@ -142,7 +142,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void connectionWindowUpdateShouldSendFrame() throws Http2Exception { public void connectionWindowUpdateShouldSendFrame() throws Http2Exception {
// Set the connection window size to zero. // Set the connection window size to zero.
controller controller
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE); .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
ByteBuf data = dummyData(10); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data);
@ -162,7 +162,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void connectionWindowUpdateShouldSendPartialFrame() throws Http2Exception { public void connectionWindowUpdateShouldSendPartialFrame() throws Http2Exception {
// Set the connection window size to zero. // Set the connection window size to zero.
controller controller
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE); .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
ByteBuf data = dummyData(10); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data);
@ -183,7 +183,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
@Test @Test
public void streamWindowUpdateShouldSendFrame() throws Http2Exception { public void streamWindowUpdateShouldSendFrame() throws Http2Exception {
// Set the stream window size to zero. // 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); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data);
@ -202,7 +202,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
@Test @Test
public void streamWindowUpdateShouldSendPartialFrame() throws Http2Exception { public void streamWindowUpdateShouldSendPartialFrame() throws Http2Exception {
// Set the stream window size to zero. // 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); ByteBuf data = dummyData(10);
send(STREAM_A, data); send(STREAM_A, data);
@ -235,10 +235,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void blockedStreamShouldSpreadDataToChildren() throws Http2Exception { public void blockedStreamShouldSpreadDataToChildren() throws Http2Exception {
// Block the connection // Block the connection
controller controller
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE); .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
// Block stream A // 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 // Try sending 10 bytes on each stream. They will be pending until we free up the
// connection. // connection.
@ -287,10 +287,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void childrenShouldNotSendDataUntilParentBlocked() throws Http2Exception { public void childrenShouldNotSendDataUntilParentBlocked() throws Http2Exception {
// Block the connection // Block the connection
controller controller
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE); .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
// Block stream B // 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 10 bytes to each.
send(STREAM_A, dummyData(10)); send(STREAM_A, dummyData(10));
@ -330,10 +330,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void parentShouldWaterFallDataToChildren() throws Http2Exception { public void parentShouldWaterFallDataToChildren() throws Http2Exception {
// Block the connection // Block the connection
controller controller
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE); .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
// Block stream B // 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. // Only send 5 to A so that it will allow data from its children.
send(STREAM_A, dummyData(5)); send(STREAM_A, dummyData(5));
@ -392,10 +392,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void reprioritizeShouldAdjustOutboundFlow() throws Http2Exception { public void reprioritizeShouldAdjustOutboundFlow() throws Http2Exception {
// Block the connection // Block the connection
controller controller
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE); .updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
// Block stream B // 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 10 bytes to each.
send(STREAM_A, dummyData(10)); send(STREAM_A, dummyData(10));
@ -437,7 +437,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void writeShouldPreferHighestWeight() throws Http2Exception { public void writeShouldPreferHighestWeight() throws Http2Exception {
// Block the connection // Block the connection
controller 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. // Root the streams at the connection and assign weights.
setPriority(STREAM_A, 0, (short) 50, false); setPriority(STREAM_A, 0, (short) 50, false);
@ -501,7 +501,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
public void samePriorityShouldWriteEqualData() throws Http2Exception { public void samePriorityShouldWriteEqualData() throws Http2Exception {
// Block the connection // Block the connection
controller 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. // Root the streams at the connection with the same weights.
setPriority(STREAM_A, 0, DEFAULT_PRIORITY_WEIGHT, false); setPriority(STREAM_A, 0, DEFAULT_PRIORITY_WEIGHT, false);

View File

@ -15,6 +15,34 @@
package io.netty.handler.codec.http2; 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.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@ -22,6 +50,9 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelPromise; import io.netty.channel.DefaultChannelPromise;
import java.util.Collections;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -29,18 +60,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; 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 * Tests for {@link DelegatingHttp2ConnectionHandlerTest} and its base class
* {@link AbstractHttp2ConnectionHandler}. * {@link AbstractHttp2ConnectionHandler}.
@ -123,16 +142,14 @@ public class DelegatingHttp2ConnectionHandlerTest {
// Simulate activation of the handler to force writing the initial settings. // Simulate activation of the handler to force writing the initial settings.
Http2Settings settings = new Http2Settings(); Http2Settings settings = new Http2Settings();
settings.allowCompressedData(true);
settings.initialWindowSize(10); settings.initialWindowSize(10);
settings.pushEnabled(true); settings.pushEnabled(true);
settings.maxConcurrentStreams(100); settings.maxConcurrentStreams(100);
settings.maxHeaderTableSize(200); settings.headerTableSize(200);
when(local.allowCompressedData()).thenReturn(true);
when(inboundFlow.initialInboundWindowSize()).thenReturn(10); when(inboundFlow.initialInboundWindowSize()).thenReturn(10);
when(local.allowPushTo()).thenReturn(true); when(local.allowPushTo()).thenReturn(true);
when(remote.maxStreams()).thenReturn(100); when(remote.maxStreams()).thenReturn(100);
when(reader.maxHeaderTableSize()).thenReturn(200); when(reader.maxHeaderTableSize()).thenReturn(200L);
handler.handlerAdded(ctx); handler.handlerAdded(ctx);
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings)); verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
@ -229,40 +246,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
@Test @Test
public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception { public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception {
when(remote.isGoAwayReceived()).thenReturn(true); when(remote.isGoAwayReceived()).thenReturn(true);
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true, true); decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true);
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10), verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
eq(true), eq(true), 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 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()); anyBoolean(), anyBoolean());
} }
@Test @Test
public void dataReadWithEndOfStreamShouldCloseRemoteSide() throws Exception { 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), 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(stream).closeRemoteSide();
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true), verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true),
eq(false), 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);
} }
@Test @Test
@ -421,14 +421,12 @@ public class DelegatingHttp2ConnectionHandlerTest {
settings.pushEnabled(true); settings.pushEnabled(true);
settings.initialWindowSize(123); settings.initialWindowSize(123);
settings.maxConcurrentStreams(456); settings.maxConcurrentStreams(456);
settings.allowCompressedData(true); settings.headerTableSize(789);
settings.maxHeaderTableSize(789);
decode().onSettingsRead(ctx, settings); decode().onSettingsRead(ctx, settings);
verify(remote).allowPushTo(true); verify(remote).allowPushTo(true);
verify(outboundFlow).initialOutboundWindowSize(123); verify(outboundFlow).initialOutboundWindowSize(123);
verify(local).maxStreams(456); verify(local).maxStreams(456);
assertTrue(handler.settings().allowCompressedData()); verify(writer).maxHeaderTableSize(789L);
verify(writer).maxHeaderTableSize(789);
// Take into account the time this was called during setup(). // Take into account the time this was called during setup().
verify(writer, times(2)).writeSettingsAck(eq(ctx), eq(promise)); verify(writer, times(2)).writeSettingsAck(eq(ctx), eq(promise));
verify(observer).onSettingsRead(eq(ctx), eq(settings)); 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)); 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 @Test
public void dataWriteAfterGoAwayShouldFail() throws Exception { public void dataWriteAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true); when(connection.isGoAway()).thenReturn(true);
@ -464,21 +446,6 @@ public class DelegatingHttp2ConnectionHandlerTest {
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception); 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 @Test
public void dataWriteShouldSucceed() throws Exception { public void dataWriteShouldSucceed() throws Exception {
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, false); handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, false);
@ -600,44 +567,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
@Test @Test
public void settingsWriteShouldNotUpdateSettings() throws Exception { public void settingsWriteShouldNotUpdateSettings() throws Exception {
Http2Settings settings = new Http2Settings(); Http2Settings settings = new Http2Settings();
settings.allowCompressedData(false);
settings.initialWindowSize(100); settings.initialWindowSize(100);
settings.pushEnabled(false); settings.pushEnabled(false);
settings.maxConcurrentStreams(1000); settings.maxConcurrentStreams(1000);
settings.maxHeaderTableSize(2000); settings.headerTableSize(2000);
handler.writeSettings(ctx, promise, settings); handler.writeSettings(ctx, promise, settings);
verify(writer).writeSettings(eq(ctx), eq(promise), eq(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 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(inboundFlow, never()).initialInboundWindowSize(eq(100));
verify(local, never()).allowPushTo(eq(false)); verify(local, never()).allowPushTo(eq(false));
verify(remote, never()).maxStreams(eq(1000)); 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 // Verify that settings values are applied on the reception of SETTINGS ACK
decode().onSettingsAckRead(ctx); decode().onSettingsAckRead(ctx);
verify(local).allowCompressedData(eq(false));
verify(inboundFlow).initialInboundWindowSize(eq(100)); verify(inboundFlow).initialInboundWindowSize(eq(100));
verify(local).allowPushTo(eq(false)); verify(local).allowPushTo(eq(false));
verify(remote).maxStreams(eq(1000)); verify(remote).maxStreams(eq(1000));
verify(reader).maxHeaderTableSize(eq(2000)); verify(reader).maxHeaderTableSize(eq(2000L));
}
@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));
} }
private static ByteBuf dummyData() { private static ByteBuf dummyData() {

View File

@ -129,7 +129,7 @@ public class Http2ConnectionRoundtripTest {
anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false), anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false),
eq(false)); eq(false));
verify(serverObserver, times(NUM_STREAMS)).onDataRead(any(ChannelHandlerContext.class), verify(serverObserver, times(NUM_STREAMS)).onDataRead(any(ChannelHandlerContext.class),
anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true), eq(false)); anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true));
} }
private void awaitRequests() throws Exception { private void awaitRequests() throws Exception {
@ -152,10 +152,10 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) boolean endOfStream, boolean endOfSegment)
throws Http2Exception { throws Http2Exception {
serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream, serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
endOfSegment, compressed); endOfSegment);
requestLatch.countDown(); requestLatch.countDown();
} }
@ -235,16 +235,9 @@ public class Http2ConnectionRoundtripTest {
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port, public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
ByteBuf protocolId, String host, String origin) throws Http2Exception { Http2Flags flags, ByteBuf payload) {
serverObserver serverObserver.onUnknownFrame(ctx, frameType, streamId, flags, payload);
.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);
requestLatch.countDown(); requestLatch.countDown();
} }

View File

@ -116,12 +116,12 @@ public class Http2FrameRoundtripTest {
@Override @Override
public void run() { public void run() {
frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF, frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF,
Unpooled.copiedBuffer(text.getBytes()), 100, true, false, false); Unpooled.copiedBuffer(text.getBytes()), 100, true, false);
} }
}); });
awaitRequests(); awaitRequests();
verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
dataCaptor.capture(), eq(100), eq(true), eq(false), eq(false)); dataCaptor.capture(), eq(100), eq(true), eq(false));
} }
@Test @Test
@ -231,10 +231,9 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void settingsFrameShouldMatch() throws Exception { public void settingsFrameShouldMatch() throws Exception {
final Http2Settings settings = new Http2Settings(); final Http2Settings settings = new Http2Settings();
settings.allowCompressedData(true);
settings.initialWindowSize(10); settings.initialWindowSize(10);
settings.maxConcurrentStreams(1000); settings.maxConcurrentStreams(1000);
settings.maxHeaderTableSize(4096); settings.headerTableSize(4096);
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@Override @Override
public void run() { public void run() {
@ -274,7 +273,7 @@ public class Http2FrameRoundtripTest {
frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false, frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false,
0, false, false); 0, false, false);
frameWriter.writeData(ctx(), newPromise(), i, 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 @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, boolean endOfSegment, boolean compressed) int padding, boolean endOfStream, boolean endOfSegment)
throws Http2Exception { throws Http2Exception {
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream, observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
endOfSegment, compressed); endOfSegment);
requestLatch.countDown(); requestLatch.countDown();
} }
@ -400,18 +399,9 @@ public class Http2FrameRoundtripTest {
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
int port, ByteBuf protocolId, String host, String origin) Http2Flags flags, ByteBuf payload) {
throws Http2Exception { observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
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);
requestLatch.countDown(); requestLatch.countDown();
} }
}); });

View File

@ -16,11 +16,9 @@
package io.netty.handler.codec.http2; package io.netty.handler.codec.http2;
import static org.junit.Assert.assertEquals; 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 static org.junit.Assert.assertTrue;
import java.util.NoSuchElementException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -37,105 +35,23 @@ public class Http2SettingsTest {
} }
@Test @Test
public void allValuesShouldBeNotSet() { public void standardSettingsShouldBeNotSet() {
assertFalse(settings.hasAllowCompressedData()); assertEquals(0, settings.size());
assertFalse(settings.hasMaxHeaderTableSize()); assertNull(settings.headerTableSize());
assertFalse(settings.hasInitialWindowSize()); assertNull(settings.initialWindowSize());
assertFalse(settings.hasMaxConcurrentStreams()); assertNull(settings.maxConcurrentStreams());
assertFalse(settings.hasPushEnabled()); assertNull(settings.pushEnabled());
} }
@Test(expected = NoSuchElementException.class) @Test
public void unsetAllowCompressedDataShouldThrow() { public void standardSettingsShouldBeSet() {
// Set everything else.
settings.maxHeaderTableSize(1);
settings.initialWindowSize(1); settings.initialWindowSize(1);
settings.maxConcurrentStreams(1); settings.maxConcurrentStreams(2);
settings.pushEnabled(true); settings.pushEnabled(true);
settings.headerTableSize(3);
settings.allowCompressedData(); assertEquals(1, (int) settings.initialWindowSize());
} assertEquals(2L, (long) settings.maxConcurrentStreams());
@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());
assertTrue(settings.pushEnabled()); assertTrue(settings.pushEnabled());
settings.pushEnabled(false); assertEquals(3L, (long) settings.headerTableSize());
assertFalse(settings.pushEnabled());
} }
} }

View File

@ -247,6 +247,42 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
return outValues; 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. * Copies the occupied entries from the source to the target array.
*/ */

View File

@ -57,7 +57,7 @@ public class Http2ClientConnectionHandler extends AbstractHttp2ConnectionHandler
private ByteBuf collectedData; private ByteBuf collectedData;
public Http2ClientConnectionHandler(ChannelPromise initPromise, ChannelPromise responsePromise) { public Http2ClientConnectionHandler(ChannelPromise initPromise, ChannelPromise responsePromise) {
this(initPromise, responsePromise, new DefaultHttp2Connection(false, false)); this(initPromise, responsePromise, new DefaultHttp2Connection(false));
} }
private Http2ClientConnectionHandler(ChannelPromise initPromise, private Http2ClientConnectionHandler(ChannelPromise initPromise,
@ -119,7 +119,7 @@ public class Http2ClientConnectionHandler extends AbstractHttp2ConnectionHandler
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception { boolean endOfStream, boolean endOfSegment) throws Http2Exception {
// Copy the data into the buffer. // Copy the data into the buffer.
int available = data.readableBytes(); int available = data.readableBytes();

View File

@ -47,7 +47,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
static final byte[] RESPONSE_BYTES = "Hello World".getBytes(CharsetUtil.UTF_8); static final byte[] RESPONSE_BYTES = "Hello World".getBytes(CharsetUtil.UTF_8);
public HelloWorldHttp2Handler() { public HelloWorldHttp2Handler() {
this(new DefaultHttp2Connection(true, false)); this(new DefaultHttp2Connection(true));
} }
private HelloWorldHttp2Handler(Http2Connection connection) { private HelloWorldHttp2Handler(Http2Connection connection) {
@ -77,7 +77,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
*/ */
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception { boolean endOfStream, boolean endOfSegment) throws Http2Exception {
if (endOfStream) { if (endOfStream) {
sendResponse(ctx(), streamId); sendResponse(ctx(), streamId);
} }