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

View File

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

View File

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

View File

@ -15,14 +15,28 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_LENGTH_MASK;
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.readUnsignedInt;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static io.netty.handler.codec.http2.Http2Exception.*;
import static io.netty.util.CharsetUtil.*;
/**
* A {@link Http2FrameReader} that supports all frame types defined by the HTTP/2 specification.
*/
@ -37,7 +51,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
private final Http2HeadersDecoder headersDecoder;
private State state = State.FRAME_HEADER;
private Http2FrameType frameType;
private byte frameType;
private int streamId;
private Http2Flags flags;
private int payloadLength;
@ -52,12 +66,12 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
}
@Override
public void maxHeaderTableSize(int max) {
headersDecoder.maxHeaderTableSize(max);
public void maxHeaderTableSize(long max) {
headersDecoder.maxHeaderTableSize((int) Math.min(max, Integer.MAX_VALUE));
}
@Override
public int maxHeaderTableSize() {
public long maxHeaderTableSize() {
return headersDecoder.maxHeaderTableSize();
}
@ -120,7 +134,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
// Read the header and prepare the unmarshaller to read the frame.
payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK;
frameType = Http2FrameType.forTypeCode(in.readUnsignedByte());
frameType = in.readByte();
flags = new Http2Flags(in.readUnsignedByte());
streamId = readUnsignedInt(in);
@ -155,22 +169,16 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
case CONTINUATION:
verifyContinuationFrame();
break;
case ALT_SVC:
verifyAltSvcFrame();
break;
case BLOCKED:
verifyBlockedFrame();
break;
default:
throw protocolError("Unsupported frame type: %s", frameType);
// Unknown frame type, could be an extension.
break;
}
// Start reading the payload for the frame.
state = State.FRAME_PAYLOAD;
}
private void
processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
private void processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
throws Http2Exception {
if (in.readableBytes() < payloadLength) {
// Wait until the entire payload has been read.
@ -212,15 +220,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
case CONTINUATION:
readContinuationFrame(payload, observer);
break;
case ALT_SVC:
readAltSvcFrame(ctx, payload, observer);
break;
case BLOCKED:
observer.onBlockedRead(ctx, streamId);
break;
default:
// Should never happen.
throw protocolError("Unsupported frame type: %s", frameType);
readUnknownFrame(ctx, payload, observer);
break;
}
// Go back to reading the next frame header.
@ -231,10 +233,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
verifyNotProcessingHeaders();
verifyPayloadLength(payloadLength);
if (!flags.isPaddingLengthValid()) {
throw protocolError("Pad high is set but pad low is not");
}
if (payloadLength < flags.getNumPaddingLengthBytes()) {
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
throw protocolError("Frame length %d too small.", payloadLength);
}
}
@ -243,18 +242,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
verifyNotProcessingHeaders();
verifyPayloadLength(payloadLength);
int lengthWithoutPriority = flags.getNumPaddingLengthBytes();
if (lengthWithoutPriority < 0) {
int requiredLength = flags.getPaddingPresenceFieldLength() + flags.getNumPriorityBytes();
if (payloadLength < requiredLength) {
throw protocolError("Frame length too small." + payloadLength);
}
if (!flags.isPaddingLengthValid()) {
throw protocolError("Pad high is set but pad low is not");
}
if (lengthWithoutPriority < flags.getNumPaddingLengthBytes()) {
throw protocolError("Frame length %d too small for padding.", payloadLength);
}
}
private void verifyPriorityFrame() throws Http2Exception {
@ -291,15 +282,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
verifyNotProcessingHeaders();
verifyPayloadLength(payloadLength);
if (!flags.isPaddingLengthValid()) {
throw protocolError("Pad high is set but pad low is not");
}
// Subtract the length of the promised stream ID field, to determine the length of the
// rest of the payload (header block fragment + payload).
int lengthWithoutPromisedId = payloadLength - INT_FIELD_LENGTH;
if (lengthWithoutPromisedId < flags.getNumPaddingLengthBytes()) {
throw protocolError("Frame length %d too small for padding.", payloadLength);
int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
if (payloadLength < minLength) {
throw protocolError("Frame length %d too small.", payloadLength);
}
}
@ -347,48 +334,25 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
+ "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
}
if (!flags.isPaddingLengthValid()) {
throw protocolError("Pad high is set but pad low is not");
}
if (payloadLength < flags.getNumPaddingLengthBytes()) {
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
throw protocolError("Frame length %d too small for padding.", payloadLength);
}
}
private void verifyAltSvcFrame() throws Http2Exception {
verifyNotProcessingHeaders();
verifyStreamOrConnectionId(streamId, "Stream ID");
verifyPayloadLength(payloadLength);
if (payloadLength < 8) {
throw protocolError("Frame length too small." + payloadLength);
}
}
private void verifyBlockedFrame() throws Http2Exception {
verifyNotProcessingHeaders();
verifyStreamOrConnectionId(streamId, "Stream ID");
if (payloadLength != 0) {
throw protocolError("Invalid frame length %d.", payloadLength);
}
}
private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload,
Http2FrameObserver observer) throws Http2Exception {
int dataPadding = flags.readPaddingLength(payload);
short padding = readPadding(payload);
// Determine how much data there is to read by removing the trailing
// padding.
int dataLength = payload.readableBytes() - dataPadding;
int dataLength = payload.readableBytes() - padding;
if (dataLength < 0) {
throw protocolError("Frame payload too small for padding.");
}
ByteBuf data = payload.readSlice(dataLength);
observer.onDataRead(ctx, streamId, data, dataPadding, flags.endOfStream(),
flags.endOfSegment(), flags.compressed());
observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream(),
flags.endOfSegment());
payload.skipBytes(payload.readableBytes());
}
@ -396,7 +360,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
Http2FrameObserver observer) throws Http2Exception {
final int headersStreamId = streamId;
final Http2Flags headersFlags = flags;
int padding = flags.readPaddingLength(payload);
final int padding = readPadding(payload);
// The callback that is invoked is different depending on whether priority information
// is present in the headers frame.
@ -415,7 +379,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
}
@Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
Http2FrameObserver observer) throws Http2Exception {
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
if (endOfHeaders) {
@ -429,7 +393,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
};
// Process the initial fragment, invoking the observer's callback if end of headers.
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
return;
}
@ -442,7 +406,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
}
@Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
Http2FrameObserver observer) throws Http2Exception {
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
if (endOfHeaders) {
@ -456,7 +420,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
// Process the initial fragment, invoking the observer's callback if end of headers.
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
}
private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
@ -480,45 +444,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
observer.onSettingsAckRead(ctx);
} else {
int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
Http2Settings settings = new Http2Settings();
Http2Settings settings = new Http2Settings(5);
for (int index = 0; index < numSettings; ++index) {
short id = payload.readUnsignedByte();
int id = payload.readUnsignedShort();
long value = payload.readUnsignedInt();
switch (id) {
case SETTINGS_HEADER_TABLE_SIZE:
if (value < 0 || value > Integer.MAX_VALUE) {
throw protocolError("Invalid value for HEADER_TABLE_SIZE: %d", value);
}
settings.maxHeaderTableSize((int) value);
break;
case SETTINGS_COMPRESS_DATA:
if (value != 0 && value != 1) {
throw protocolError("Invalid value for COMPRESS_DATA: %d", value);
}
settings.allowCompressedData(value == 1);
break;
case SETTINGS_ENABLE_PUSH:
if (value != 0 && value != 1) {
throw protocolError("Invalid value for ENABLE_PUSH: %d", value);
}
settings.pushEnabled(value == 1);
break;
case SETTINGS_INITIAL_WINDOW_SIZE:
if (value < 0 || value > Integer.MAX_VALUE) {
throw protocolError("Invalid value for INITIAL_WINDOW_SIZE: %d", value);
}
settings.initialWindowSize((int) value);
break;
case SETTINGS_MAX_CONCURRENT_STREAMS:
if (value < 0 || value > Integer.MAX_VALUE) {
throw protocolError("Invalid value for MAX_CONCURRENT_STREAMS: %d",
value);
}
settings.maxConcurrentStreams((int) value);
break;
default:
throw protocolError("Unsupport setting: %d", id);
}
settings.put(id, value);
}
observer.onSettingsRead(ctx, settings);
}
@ -527,7 +457,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
Http2FrameObserver observer) throws Http2Exception {
final int pushPromiseStreamId = streamId;
int padding = flags.readPaddingLength(payload);
final int padding = readPadding(payload);
final int promisedStreamId = readUnsignedInt(payload);
// Create a handler that invokes the observer when the header block is complete.
@ -538,7 +468,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
}
@Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
Http2FrameObserver observer) throws Http2Exception {
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
if (endOfHeaders) {
@ -552,7 +482,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
// Process the initial fragment, invoking the observer's callback if end of headers.
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
}
private void readPingFrame(ChannelHandlerContext ctx, ByteBuf payload,
@ -581,30 +511,26 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
private void readContinuationFrame(ByteBuf payload, Http2FrameObserver observer)
throws Http2Exception {
int padding = flags.readPaddingLength(payload);
// Process the initial fragment, invoking the observer's callback if end of headers.
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes() - padding);
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment, padding,
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes());
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment,
observer);
}
private void readAltSvcFrame(ChannelHandlerContext ctx, ByteBuf payload,
Http2FrameObserver observer) throws Http2Exception {
long maxAge = payload.readUnsignedInt();
int port = payload.readUnsignedShort();
payload.skipBytes(1);
short protocolIdLength = payload.readUnsignedByte();
ByteBuf protocolId = payload.readSlice(protocolIdLength);
short hostLength = payload.readUnsignedByte();
String host = payload.toString(payload.readerIndex(), hostLength, UTF_8);
payload.skipBytes(hostLength);
String origin = null;
if (payload.isReadable()) {
origin = payload.toString(UTF_8);
payload.skipBytes(payload.readableBytes());
private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload, Http2FrameObserver observer)
throws Http2Exception {
payload = payload.readSlice(payload.readableBytes());
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
}
/**
* If padding is present in the payload, reads the next byte as padding. Otherwise, returns zero.
*/
private short readPadding(ByteBuf payload) {
if (!flags.paddingPresent()) {
return 0;
}
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
return payload.readUnsignedByte();
}
/**
@ -625,11 +551,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
*
* @param endOfHeaders whether the fragment is the last in the header block.
* @param fragment the fragment of the header block to be added.
* @param padding the amount of padding to be supplied to the {@link Http2FrameObserver}
* callback.
* @param observer the observer to be notified if the header block is completed.
*/
abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
abstract void processFragment(boolean endOfHeaders, ByteBuf fragment,
Http2FrameObserver observer) throws Http2Exception;
final HeadersBuilder builder() {

View File

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

View File

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

View File

@ -57,7 +57,7 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
};
private final Http2Connection connection;
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
private int initialWindowSize = DEFAULT_WINDOW_SIZE;
public DefaultHttp2OutboundFlowController(Http2Connection connection) {
if (connection == null) {
@ -147,11 +147,6 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
}
}
@Override
public void setBlocked(int streamId) throws Http2Exception {
// Ignore blocked frames. Just rely on window updates.
}
@Override
public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream,
boolean endSegment, boolean compressed, FrameWriter frameWriter) throws Http2Exception {

View File

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

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

View File

@ -15,16 +15,15 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.format;
import static io.netty.util.CharsetUtil.UTF_8;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action;
import static io.netty.handler.codec.http2.Http2Error.*;
import static io.netty.handler.codec.http2.Http2Exception.*;
import static io.netty.util.CharsetUtil.*;
/**
* Constants and utility method used for encoding/decoding HTTP2 frames.
*/
@ -36,7 +35,7 @@ public final class Http2CodecUtil {
public static final int CONNECTION_STREAM_ID = 0;
public static final int HTTP_UPGRADE_STREAM_ID = 1;
public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings";
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-12";
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-13";
public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383;
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
@ -45,19 +44,19 @@ public final class Http2CodecUtil {
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
public static final int FRAME_HEADER_LENGTH = 8;
public static final int FRAME_LENGTH_MASK = 0x3FFF;
public static final int SETTING_ENTRY_LENGTH = 5;
public static final int SETTING_ENTRY_LENGTH = 6;
public static final int PRIORITY_ENTRY_LENGTH = 5;
public static final int INT_FIELD_LENGTH = 4;
public static final short MAX_WEIGHT = 256;
public static final short MIN_WEIGHT = 1;
public static final short MAX_WEIGHT = (short) 256;
public static final short MIN_WEIGHT = (short) 1;
public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
public static final short SETTINGS_ENABLE_PUSH = 2;
public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3;
public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4;
public static final short SETTINGS_COMPRESS_DATA = 5;
public static final int SETTINGS_HEADER_TABLE_SIZE = 1;
public static final int SETTINGS_ENABLE_PUSH = 2;
public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4;
public static final int DEFAULT_FLOW_CONTROL_WINDOW_SIZE = 65535;
public static final int DEFAULT_WINDOW_SIZE = 65535;
public static final boolean DEFAULT_ENABLE_PUSH = true;
public static final short DEFAULT_PRIORITY_WEIGHT = 16;
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
@ -163,58 +162,15 @@ public final class Http2CodecUtil {
/**
* Writes an HTTP/2 frame header to the output buffer.
*/
public static void writeFrameHeader(ByteBuf out, int payloadLength, Http2FrameType type,
public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
Http2Flags flags, int streamId) {
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(type.typeCode());
out.writeByte(type);
out.writeByte(flags.value());
out.writeInt(streamId);
}
/**
* Calculates the HTTP/2 SETTINGS payload length for the serialized representation
* of the given settings.
*/
public static int calcSettingsPayloadLength(Http2Settings settings) {
int numFields = 0;
numFields += settings.hasAllowCompressedData() ? 1 : 0;
numFields += settings.hasMaxHeaderTableSize() ? 1 : 0;
numFields += settings.hasInitialWindowSize() ? 1 : 0;
numFields += settings.hasMaxConcurrentStreams() ? 1 : 0;
numFields += settings.hasPushEnabled() ? 1 : 0;
return SETTING_ENTRY_LENGTH * numFields;
}
/**
* Serializes the settings to the output buffer in the format of an HTTP/2 SETTINGS frame
* payload.
*/
public static void writeSettingsPayload(Http2Settings settings, ByteBuf out) {
if (settings.hasAllowCompressedData()) {
out.writeByte(SETTINGS_COMPRESS_DATA);
writeUnsignedInt(settings.allowCompressedData() ? 1L : 0L, out);
}
if (settings.hasMaxHeaderTableSize()) {
out.writeByte(SETTINGS_HEADER_TABLE_SIZE);
writeUnsignedInt(settings.maxHeaderTableSize(), out);
}
if (settings.hasInitialWindowSize()) {
out.writeByte(SETTINGS_INITIAL_WINDOW_SIZE);
writeUnsignedInt(settings.initialWindowSize(), out);
}
if (settings.hasMaxConcurrentStreams()) {
out.writeByte(SETTINGS_MAX_CONCURRENT_STREAMS);
writeUnsignedInt(settings.maxConcurrentStreams(), out);
}
if (settings.hasPushEnabled()) {
// Only write the enable push flag from client endpoints.
out.writeByte(SETTINGS_ENABLE_PUSH);
writeUnsignedInt(settings.pushEnabled() ? 1L : 0L, out);
}
}
/**
* Fails the given promise with the cause and then re-throws the cause.
*/

View File

@ -177,16 +177,6 @@ public interface Http2Connection {
*/
void maxStreams(int maxStreams);
/**
* Indicates whether or not this endpoint allows compression.
*/
boolean allowCompressedData();
/**
* Sets whether or not this endpoint allows compression.
*/
void allowCompressedData(boolean allow);
/**
* Gets the ID of the stream last successfully created by this endpoint.
*/

View File

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

View File

@ -15,28 +15,20 @@
package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf;
/**
* Provides utility methods for accessing specific flags as defined by the HTTP/2 spec.
*/
public class Http2Flags {
public static final Http2Flags EMPTY = new Http2Flags();
public static final Http2Flags ACK_ONLY = new Builder().ack(true).build();
public final class Http2Flags {
public static final short END_STREAM = 0x1;
public static final short END_SEGMENT = 0x2;
public static final short END_HEADERS = 0x4;
public static final short ACK = 0x1;
public static final short PAD_LOW = 0x8;
public static final short PAD_HIGH = 0x10;
public static final short PADDED = 0x8;
public static final short PRIORITY = 0x20;
public static final short COMPRESSED = 0x20;
private final short value;
private short value;
private Http2Flags() {
this((short) 0);
public Http2Flags() {
}
public Http2Flags(short value) {
@ -55,7 +47,7 @@ public class Http2Flags {
* frames.
*/
public boolean endOfStream() {
return endOfStream(value);
return isFlagSet(END_STREAM);
}
/**
@ -63,7 +55,7 @@ public class Http2Flags {
* frames.
*/
public boolean endOfSegment() {
return endOfSegment(value);
return isFlagSet(END_SEGMENT);
}
/**
@ -71,7 +63,7 @@ public class Http2Flags {
* PUSH_PROMISE, and CONTINUATION frames.
*/
public boolean endOfHeaders() {
return endOfHeaders(value);
return isFlagSet(END_HEADERS);
}
/**
@ -79,7 +71,7 @@ public class Http2Flags {
* dependency, and weight fields in a HEADERS frame.
*/
public boolean priorityPresent() {
return priorityPresent(value);
return isFlagSet(PRIORITY);
}
/**
@ -87,46 +79,15 @@ public class Http2Flags {
* SETTINGS and PING frames.
*/
public boolean ack() {
return ack(value);
return isFlagSet(ACK);
}
/**
* For DATA frames, indicates that the data is compressed using gzip compression.
*/
public boolean compressed() {
return compressed(value);
}
/**
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
* For frames that include padding, indicates if the {@link #PADDED} field is present. Only
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
*/
public boolean padLowPresent() {
return padLowPresent(value);
}
/**
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
*/
public boolean padHighPresent() {
return padHighPresent(value);
}
/**
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
* be set.
*/
public boolean isPaddingLengthValid() {
return isPaddingLengthValid(value);
}
/**
* Gets the number of bytes expected in the padding length field of the payload. This is
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
*/
public int getNumPaddingLengthBytes() {
return getNumPaddingLengthBytes(value);
public boolean paddingPresent() {
return isFlagSet(PADDED);
}
/**
@ -134,14 +95,81 @@ public class Http2Flags {
* by the {@link #priorityPresent()} flag.
*/
public int getNumPriorityBytes() {
return getNumPriorityBytes(value);
return priorityPresent() ? 5 : 0;
}
/**
* Reads the variable-length padding length field from the payload.
* Gets the length in bytes of the padding presence field expected in the payload. This is
* determined by the {@link #paddingPresent()} flag.
*/
public int readPaddingLength(ByteBuf payload) {
return readPaddingLength(value, payload);
public int getPaddingPresenceFieldLength() {
return paddingPresent() ? 1 : 0;
}
/**
* Sets the {@link #END_STREAM} flag.
*/
public Http2Flags endOfStream(boolean endOfStream) {
return setFlag(endOfStream, END_STREAM);
}
/**
* Sets the {@link #END_SEGMENT} flag.
*/
public Http2Flags endOfSegment(boolean endOfSegment) {
return setFlag(endOfSegment, END_SEGMENT);
}
/**
* Sets the {@link #END_HEADERS} flag.
*/
public Http2Flags endOfHeaders(boolean endOfHeaders) {
return setFlag(endOfHeaders, END_HEADERS);
}
/**
* Sets the {@link #PRIORITY} flag.
*/
public Http2Flags priorityPresent(boolean priorityPresent) {
return setFlag(priorityPresent, PRIORITY);
}
/**
* Sets the {@link #PADDED} flag.
*/
public Http2Flags paddingPresent(boolean paddingPresent) {
return setFlag(paddingPresent, PADDED);
}
/**
* Sets the {@link #ACK} flag.
*/
public Http2Flags ack(boolean ack) {
return setFlag(ack, ACK);
}
/**
* Generic method to set any flag.
* @param on if the flag should be enabled or disabled.
* @param mask the mask that identifies the bit for the flag.
* @return this instance.
*/
public Http2Flags setFlag(boolean on, short mask) {
if (on) {
value |= mask;
} else {
value &= ~mask;
}
return this;
}
/**
* Indicates whether or not a particular flag is set.
* @param mask the mask identifying the bit for the particular flag being tested
* @return {@code true} if the flag is set
*/
public boolean isFlagSet(short mask) {
return (value & mask) != 0;
}
@Override
@ -186,298 +214,10 @@ public class Http2Flags {
if (endOfSegment()) {
builder.append("END_OF_SEGMENT,");
}
if (padHighPresent()) {
builder.append("PAD_HIGH,");
}
if (padLowPresent()) {
builder.append("PAD_LOW,");
if (paddingPresent()) {
builder.append("PADDING_PRESENT,");
}
builder.append(')');
return builder.toString();
}
/**
* Shortcut for creating a new {@link Builder} instance.
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* Builder for instances of {@link Http2Flags}.
*/
public static final class Builder {
private short value;
/**
* Sets the {@link #END_STREAM} flag.
*/
public Builder endOfStream(boolean endOfStream) {
return setFlag(endOfStream, END_STREAM);
}
/**
* Sets the {@link #END_SEGMENT} flag.
*/
public Builder endOfSegment(boolean endOfSegment) {
return setFlag(endOfSegment, END_SEGMENT);
}
/**
* Sets the {@link #END_HEADERS} flag.
*/
public Builder endOfHeaders(boolean endOfHeaders) {
return setFlag(endOfHeaders, END_HEADERS);
}
/**
* Sets the {@link #PRIORITY} flag.
*/
public Builder priorityPresent(boolean priorityPresent) {
return setFlag(priorityPresent, PRIORITY);
}
/**
* Sets the {@link #ACK} flag.
*/
public Builder ack(boolean ack) {
return setFlag(ack, ACK);
}
/**
* Sets the {@link #COMPRESSED} flag.
*/
public Builder compressed(boolean compressed) {
return setFlag(compressed, COMPRESSED);
}
/**
* Sets the padding flags in the given flags value as appropriate based on the padding
* length.
*/
public Builder setPaddingFlags(int paddingLength) {
if (paddingLength > 255) {
value |= PAD_HIGH;
}
if (paddingLength > 0) {
value |= PAD_LOW;
}
return this;
}
/**
* Determines whether the {@link #END_STREAM} flag is set. Only applies to DATA and HEADERS
* frames.
*/
public boolean endOfStream() {
return Http2Flags.endOfStream(value);
}
/**
* Determines whether the {@link #END_SEGMENT} flag is set. Only applies to DATA and HEADERS
* frames.
*/
public boolean endOfSegment() {
return Http2Flags.endOfSegment(value);
}
/**
* Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
* PUSH_PROMISE, and CONTINUATION frames.
*/
public boolean endOfHeaders() {
return Http2Flags.endOfHeaders(value);
}
/**
* Determines whether the flag is set indicating the presence of the exclusive, stream
* dependency, and weight fields in a HEADERS frame.
*/
public boolean priorityPresent() {
return Http2Flags.priorityPresent(value);
}
/**
* Determines whether the flag is set indicating that this frame is an ACK. Only applies for
* SETTINGS and PING frames.
*/
public boolean ack() {
return Http2Flags.ack(value);
}
/**
* For DATA frames, indicates that the data is compressed using gzip compression.
*/
public boolean compressed() {
return Http2Flags.compressed(value);
}
/**
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
*/
public boolean padLowPresent() {
return Http2Flags.padLowPresent(value);
}
/**
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
*/
public boolean padHighPresent() {
return Http2Flags.padHighPresent(value);
}
/**
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
* be set.
*/
public boolean isPaddingLengthValid() {
return Http2Flags.isPaddingLengthValid(value);
}
/**
* Gets the number of bytes expected in the padding length field of the payload. This is
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
*/
public int getNumPaddingLengthBytes() {
return Http2Flags.getNumPaddingLengthBytes(value);
}
/**
* Gets the number of bytes expected for the priority fields of the payload. This is determined
* by the {@link #priorityPresent()} flag.
*/
public int getNumPriorityBytes() {
return Http2Flags.getNumPriorityBytes(value);
}
/**
* Reads the variable-length padding length field from the payload.
*/
public int readPaddingLength(ByteBuf payload) {
return Http2Flags.readPaddingLength(value, payload);
}
/**
* Builds a new {@link Http2Flags} instance.
*/
public Http2Flags build() {
return new Http2Flags(value);
}
private Builder setFlag(boolean on, short mask) {
if (on) {
value |= mask;
} else {
value &= ~mask;
}
return this;
}
}
/**
* Determines whether the {@link #END_STREAM} flag is set. Only applies to DATA and HEADERS
* frames.
*/
public static boolean endOfStream(short value) {
return isFlagSet(value, END_STREAM);
}
/**
* Determines whether the {@link #END_SEGMENT} flag is set. Only applies to DATA and HEADERS
* frames.
*/
public static boolean endOfSegment(short value) {
return isFlagSet(value, END_SEGMENT);
}
/**
* Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
* PUSH_PROMISE, and CONTINUATION frames.
*/
public static boolean endOfHeaders(short value) {
return isFlagSet(value, END_HEADERS);
}
/**
* Determines whether the flag is set indicating the presence of the exclusive, stream
* dependency, and weight fields in a HEADERS frame.
*/
public static boolean priorityPresent(short value) {
return isFlagSet(value, PRIORITY);
}
/**
* Determines whether the flag is set indicating that this frame is an ACK. Only applies for
* SETTINGS and PING frames.
*/
public static boolean ack(short value) {
return isFlagSet(value, ACK);
}
/**
* For DATA frames, indicates that the data is compressed using gzip compression.
*/
public static boolean compressed(short value) {
return isFlagSet(value, COMPRESSED);
}
/**
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
*/
public static boolean padLowPresent(short value) {
return isFlagSet(value, PAD_LOW);
}
/**
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
*/
public static boolean padHighPresent(short value) {
return isFlagSet(value, PAD_HIGH);
}
/**
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
* be set.
*/
public static boolean isPaddingLengthValid(short value) {
return padHighPresent(value) ? padLowPresent(value) : true;
}
/**
* Gets the number of bytes expected in the padding length field of the payload. This is
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
*/
public static int getNumPaddingLengthBytes(short value) {
return (padHighPresent(value) ? 1 : 0) + (padLowPresent(value) ? 1 : 0);
}
/**
* Gets the number of bytes expected for the priority fields of the payload. This is determined
* by the {@link #priorityPresent()} flag.
*/
public static int getNumPriorityBytes(short value) {
return priorityPresent(value) ? 5 : 0;
}
/**
* Reads the variable-length padding length field from the payload.
*/
public static int readPaddingLength(short value, ByteBuf payload) {
int paddingLength = 0;
if (padHighPresent(value)) {
paddingLength += payload.readUnsignedByte() * 256;
}
if (padLowPresent(value)) {
paddingLength += payload.readUnsignedByte();
}
return paddingLength;
}
private static boolean isFlagSet(short value, short mask) {
return (value & mask) != 0;
}
}

View File

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

View File

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

View File

@ -34,10 +34,9 @@ public interface Http2FrameObserver {
* @param endOfStream Indicates whether this is the last frame to be sent from the remote
* endpoint for this stream.
* @param endOfSegment Indicates whether this frame is the end of the current segment.
* @param compressed Indicates whether or not the payload is compressed with gzip encoding.
*/
void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception;
boolean endOfStream, boolean endOfSegment) throws Http2Exception;
/**
* Handles an inbound HEADERS frame.
@ -162,26 +161,13 @@ public interface Http2FrameObserver {
throws Http2Exception;
/**
* Handles an inbound ALT_SVC frame.
* Handler for a frame not defined by the HTTP/2 spec.
*
* @param ctx the context from the handler where the frame was read.
* @param streamId the stream.
* @param maxAge the freshness lifetime of the alternative service association.
* @param port the port that the alternative service is available upon.
* @param protocolId the ALPN protocol identifier of the alternative service. If this buffer
* needs to be retained by the observer they must make a copy.
* @param host the host that the alternative service is available upon.
* @param origin an optional origin that the alternative service is available upon. May be
* {@code null}.
* @param frameType the frame type from the HTTP/2 header.
* @param streamId the stream the frame was sent on.
* @param flags the flags in the frame header.
* @param payload the payload of the frame.
*/
void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
ByteBuf protocolId, String host, String origin) throws Http2Exception;
/**
* Handles an inbound BLOCKED frame.
*
* @param ctx the context from the handler where the frame was read.
* @param streamId the stream that is blocked or 0 if the entire connection is blocked.
*/
void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception;
void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload);
}

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.
*/
void maxHeaderTableSize(int max);
void maxHeaderTableSize(long max);
/**
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
*/
int maxHeaderTableSize();
long maxHeaderTableSize();
/**
* Closes this reader and frees any allocated resources.

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 endStream indicates if this is the last frame to be sent for the stream.
* @param endSegment indicates if this is the last frame in the current segment.
* @param compressed indicates whether the data is compressed using gzip encoding.
* @return the future for the write.
*/
ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed);
ByteBuf data, int padding, boolean endStream, boolean endSegment);
/**
* Writes a HEADERS frame to the remote endpoint.
@ -179,31 +178,18 @@ public interface Http2FrameWriter extends Closeable {
int streamId, int windowSizeIncrement);
/**
* Writes a ALT_SVC frame to the remote endpoint.
* Generic write method for any HTTP/2 frame. This allows writing of non-standard frames.
*
* @param ctx the context to use for writing.
* @param promise the promise for the write.
* @param streamId the stream.
* @param maxAge the freshness lifetime of the alternative service association.
* @param port the port that the alternative service is available upon.
* @param protocolId the ALPN protocol identifier of the alternative service.
* @param host the host that the alternative service is available upon.
* @param origin an optional origin that the alternative service is available upon. May be
* {@code null}.
* @param frameType the frame type identifier.
* @param streamId the stream for which to send the frame.
* @param flags the flags to write for this frame.
* @param payload the payload to write for this frame.
* @return the future for the write.
*/
ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
long maxAge, int port, ByteBuf protocolId, String host, String origin);
/**
* Writes a BLOCKED frame to the remote endpoint.
*
* @param ctx the context to use for writing.
* @param promise the promise for the write.
* @param streamId the stream that is blocked or 0 if the entire connection is blocked.
* @return the future for the write.
*/
ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise, int streamId);
ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise, byte frameType,
int streamId, Http2Flags flags, ByteBuf payload);
/**
* Closes this writer and frees any allocated resources.
@ -214,10 +200,10 @@ public interface Http2FrameWriter extends Closeable {
/**
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
*/
void maxHeaderTableSize(int max) throws Http2Exception;
void maxHeaderTableSize(long max) throws Http2Exception;
/**
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
*/
int maxHeaderTableSize();
long maxHeaderTableSize();
}

View File

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

View File

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

View File

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

View File

@ -64,15 +64,6 @@ public interface Http2OutboundFlowController {
*/
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
/**
* Indicates that the given stream or the entire connection is blocked and that no more messages
* should be sent.
*
* @param streamId the stream ID that is blocked or zero if the entire connection is blocked.
* @throws Http2Exception thrown if a protocol-related error occurred.
*/
void setBlocked(int streamId) throws Http2Exception;
/**
* Sends the frame with outbound flow control applied. The frame may be written at a later time,
* depending on whether the remote endpoint can receive the frame now.

View File

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

View File

@ -14,6 +14,13 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeader;
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
@ -27,12 +34,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static io.netty.handler.codec.base64.Base64Dialect.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static io.netty.handler.codec.http2.Http2Flags.*;
import static io.netty.handler.codec.http2.Http2FrameType.*;
/**
* Server-side codec for performing a cleartext upgrade from HTTP/1.x to HTTP/2.
*/
@ -138,8 +139,9 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
final Http2Settings decodedSettings = new Http2Settings();
frameReader.readFrame(ctx, frame, new Http2FrameAdapter() {
@Override
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
decodedSettings.copy(settings);
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)
throws Http2Exception {
decodedSettings.copyFrom(settings);
}
});
return decodedSettings;
@ -153,7 +155,7 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
*/
private static ByteBuf createSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload) {
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
writeFrameHeader(frame, payload.readableBytes(), SETTINGS, EMPTY, 0);
writeFrameHeader(frame, payload.readableBytes(), SETTINGS, new Http2Flags(), 0);
frame.writeBytes(payload);
payload.release();
return frame;

View File

@ -15,258 +15,112 @@
package io.netty.handler.codec.http2;
import java.util.NoSuchElementException;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_ENABLE_PUSH;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
import io.netty.util.collection.IntObjectHashMap;
/**
* Settings for one endpoint in an HTTP/2 connection. Each of the values are optional as defined in
* the spec for the SETTINGS frame.
* the spec for the SETTINGS frame. Permits storage of arbitrary key/value pairs but provides helper
* methods for standard settings.
*/
public class Http2Settings {
private static final byte MAX_HEADER_TABLE_SIZE_MASK = 0x1;
private static final byte PUSH_ENABLED_MASK = 0x2;
private static final byte MAX_CONCURRENT_STREAMS_MASK = 0x4;
private static final byte INITIAL_WINDOW_SIZE_MASK = 0x8;
private static final byte ALLOW_COMPRESSION_MASK = 0x10;
public final class Http2Settings extends IntObjectHashMap<Long> {
private byte enabled;
private int maxHeaderTableSize;
private boolean pushEnabled;
private int maxConcurrentStreams;
private int initialWindowSize;
private boolean allowCompressedData;
/**
* Indicates whether or not the headerTableSize value is available.
*/
public boolean hasMaxHeaderTableSize() {
return isEnabled(MAX_HEADER_TABLE_SIZE_MASK);
public Http2Settings() {
}
/**
* Gets the maximum HPACK header table size or throws {@link NoSuchElementException} if the
* value has not been set.
*/
public int maxHeaderTableSize() {
if (!hasMaxHeaderTableSize()) {
throw new NoSuchElementException("headerTableSize");
}
return maxHeaderTableSize;
public Http2Settings(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
/**
* Sets the maximum HPACK header table size to the specified value.
*/
public Http2Settings maxHeaderTableSize(int headerTableSize) {
if (headerTableSize < 0) {
throw new IllegalArgumentException("headerTableSize must be >= 0");
}
enable(MAX_HEADER_TABLE_SIZE_MASK);
maxHeaderTableSize = headerTableSize;
return this;
}
/**
* Indicates whether or not the pushEnabled value is available.
*/
public boolean hasPushEnabled() {
return isEnabled(PUSH_ENABLED_MASK);
}
/**
* Gets whether or not server push is enabled or throws {@link NoSuchElementException} if the
* value has not been set.
*/
public boolean pushEnabled() {
if (!hasPushEnabled()) {
throw new NoSuchElementException("pushEnabled");
}
return pushEnabled;
}
/**
* Sets whether or not server push is enabled.
*/
public Http2Settings pushEnabled(boolean pushEnabled) {
enable(PUSH_ENABLED_MASK);
this.pushEnabled = pushEnabled;
return this;
}
/**
* Indicates whether or not the maxConcurrentStreams value is available.
*/
public boolean hasMaxConcurrentStreams() {
return isEnabled(MAX_CONCURRENT_STREAMS_MASK);
}
/**
* Gets the maximum allowed concurrent streams or throws {@link NoSuchElementException} if the
* value has not been set.
*/
public int maxConcurrentStreams() {
if (!hasMaxConcurrentStreams()) {
throw new NoSuchElementException("maxConcurrentStreams");
}
return maxConcurrentStreams;
}
/**
* Sets the maximum allowed concurrent streams to the specified value.
*/
public Http2Settings maxConcurrentStreams(int maxConcurrentStreams) {
if (maxConcurrentStreams < 0) {
throw new IllegalArgumentException("maxConcurrentStreams must be >= 0");
}
enable(MAX_CONCURRENT_STREAMS_MASK);
this.maxConcurrentStreams = maxConcurrentStreams;
return this;
}
/**
* Indicates whether or not the initialWindowSize value is available.
*/
public boolean hasInitialWindowSize() {
return isEnabled(INITIAL_WINDOW_SIZE_MASK);
}
/**
* Gets the initial flow control window size or throws {@link NoSuchElementException} if the
* value has not been set.
*/
public int initialWindowSize() {
if (!hasInitialWindowSize()) {
throw new NoSuchElementException("initialWindowSize");
}
return initialWindowSize;
}
/**
* Sets the initial flow control window size to the specified value.
*/
public Http2Settings initialWindowSize(int initialWindowSize) {
if (initialWindowSize < 0) {
throw new IllegalArgumentException("initialWindowSize must be >= 0");
}
enable(INITIAL_WINDOW_SIZE_MASK);
this.initialWindowSize = initialWindowSize;
return this;
}
/**
* Indicates whether or not the allowCompressedData value is available.
*/
public boolean hasAllowCompressedData() {
return isEnabled(ALLOW_COMPRESSION_MASK);
}
/**
* Gets whether the endpoint allows compressed data or throws {@link NoSuchElementException} if
* the value has not been set.
*/
public boolean allowCompressedData() {
if (!hasAllowCompressedData()) {
throw new NoSuchElementException("allowCompressedData");
}
return allowCompressedData;
}
/**
* Sets whether or not the endpoing allows compressed data.
*/
public Http2Settings allowCompressedData(boolean allowCompressedData) {
enable(ALLOW_COMPRESSION_MASK);
this.allowCompressedData = allowCompressedData;
return this;
}
/**
* Overwrites this settings object with the values of the given settings.
*
* @param source the source that will overwrite the current settings.
* @return this object.
*/
public Http2Settings copy(Http2Settings source) {
enabled = source.enabled;
allowCompressedData = source.allowCompressedData;
initialWindowSize = source.initialWindowSize;
maxConcurrentStreams = source.maxConcurrentStreams;
maxHeaderTableSize = source.maxHeaderTableSize;
pushEnabled = source.pushEnabled;
return this;
public Http2Settings(int initialCapacity) {
super(initialCapacity);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (allowCompressedData ? 1231 : 1237);
result = prime * result + enabled;
result = prime * result + maxHeaderTableSize;
result = prime * result + initialWindowSize;
result = prime * result + maxConcurrentStreams;
result = prime * result + (pushEnabled ? 1231 : 1237);
return result;
public Long put(int key, Long value) {
verifyStandardSetting(key, value);
return super.put(key, value);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Http2Settings other = (Http2Settings) obj;
if (allowCompressedData != other.allowCompressedData) {
return false;
}
if (enabled != other.enabled) {
return false;
}
if (maxHeaderTableSize != other.maxHeaderTableSize) {
return false;
}
if (initialWindowSize != other.initialWindowSize) {
return false;
}
if (maxConcurrentStreams != other.maxConcurrentStreams) {
return false;
}
return pushEnabled == other.pushEnabled;
public Long headerTableSize() {
return get(SETTINGS_HEADER_TABLE_SIZE);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Http2Settings [");
if (hasMaxHeaderTableSize()) {
builder.append("maxHeaderTableSize=").append(maxHeaderTableSize).append(',');
}
if (hasPushEnabled()) {
builder.append("pushEnabled=").append(pushEnabled).append(',');
}
if (hasMaxConcurrentStreams()) {
builder.append("maxConcurrentStreams=").append(maxConcurrentStreams).append(',');
}
if (hasInitialWindowSize()) {
builder.append("initialWindowSize=").append(initialWindowSize).append(',');
}
if (hasAllowCompressedData()) {
builder.append("allowCompressedData=").append(allowCompressedData).append(',');
}
builder.append(']');
return builder.toString();
public Http2Settings headerTableSize(long value) {
put(SETTINGS_HEADER_TABLE_SIZE, value);
return this;
}
private void enable(int mask) {
enabled |= mask;
public Boolean pushEnabled() {
Long value = get(SETTINGS_ENABLE_PUSH);
if (value == null) {
return null;
}
return value != 0L;
}
private boolean isEnabled(int mask) {
return (enabled & mask) > 0;
public Http2Settings pushEnabled(boolean enabled) {
put(SETTINGS_ENABLE_PUSH, enabled? 1L : 0L);
return this;
}
public Long maxConcurrentStreams() {
return get(SETTINGS_MAX_CONCURRENT_STREAMS);
}
public Http2Settings maxConcurrentStreams(long value) {
put(SETTINGS_MAX_CONCURRENT_STREAMS, value);
return this;
}
public Integer initialWindowSize() {
Long value = get(SETTINGS_INITIAL_WINDOW_SIZE);
if (value == null) {
return null;
}
return value.intValue();
}
public Http2Settings initialWindowSize(int value) {
put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value);
return this;
}
public Http2Settings copyFrom(Http2Settings settings) {
clear();
putAll(settings);
return this;
}
private void verifyStandardSetting(int key, Long value) {
if (value == null) {
throw new NullPointerException("value");
}
switch (key) {
case SETTINGS_HEADER_TABLE_SIZE:
if (value < 0L || value > MAX_UNSIGNED_INT) {
throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: " + value);
}
break;
case SETTINGS_ENABLE_PUSH:
if (value != 0L && value != 1L) {
throw new IllegalArgumentException("Setting ENABLE_PUSH is invalid: " + value);
}
break;
case SETTINGS_MAX_CONCURRENT_STREAMS:
if (value < 0L || value > MAX_UNSIGNED_INT) {
throw new IllegalArgumentException("Setting MAX_CONCURRENT_STREAMS is invalid: " + value);
}
break;
case SETTINGS_INITIAL_WINDOW_SIZE:
if (value < 0L || value > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Setting INITIAL_WINDOW_SIZE is invalid: " + value);
}
break;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,34 @@
package io.netty.handler.codec.http2;
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.buffer.Unpooled.wrappedBuffer;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
import static io.netty.handler.codec.http2.Http2CodecUtil.emptyPingBuf;
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Headers.EMPTY_HEADERS;
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL;
import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_LOCAL;
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyShort;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel;
@ -22,6 +50,9 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelPromise;
import java.util.Collections;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -29,18 +60,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
import static io.netty.buffer.Unpooled.*;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static io.netty.handler.codec.http2.Http2Error.*;
import static io.netty.handler.codec.http2.Http2Exception.*;
import static io.netty.handler.codec.http2.Http2Headers.*;
import static io.netty.handler.codec.http2.Http2Stream.State.*;
import static io.netty.util.CharsetUtil.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Tests for {@link DelegatingHttp2ConnectionHandlerTest} and its base class
* {@link AbstractHttp2ConnectionHandler}.
@ -123,16 +142,14 @@ public class DelegatingHttp2ConnectionHandlerTest {
// Simulate activation of the handler to force writing the initial settings.
Http2Settings settings = new Http2Settings();
settings.allowCompressedData(true);
settings.initialWindowSize(10);
settings.pushEnabled(true);
settings.maxConcurrentStreams(100);
settings.maxHeaderTableSize(200);
when(local.allowCompressedData()).thenReturn(true);
settings.headerTableSize(200);
when(inboundFlow.initialInboundWindowSize()).thenReturn(10);
when(local.allowPushTo()).thenReturn(true);
when(remote.maxStreams()).thenReturn(100);
when(reader.maxHeaderTableSize()).thenReturn(200);
when(reader.maxHeaderTableSize()).thenReturn(200L);
handler.handlerAdded(ctx);
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
@ -229,40 +246,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
@Test
public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception {
when(remote.isGoAwayReceived()).thenReturn(true);
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true, true);
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true);
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
eq(true), eq(true), eq(true), any(Http2InboundFlowController.FrameWriter.class));
eq(true), eq(true), any(Http2InboundFlowController.FrameWriter.class));
// Verify that the event was absorbed and not propagated to the oberver.
verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(),
verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(),
anyBoolean(), anyBoolean());
}
@Test
public void dataReadWithEndOfStreamShouldCloseRemoteSide() throws Exception {
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, false, false);
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, false);
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
eq(true), eq(false), eq(false), any(Http2InboundFlowController.FrameWriter.class));
eq(true), eq(false), any(Http2InboundFlowController.FrameWriter.class));
verify(stream).closeRemoteSide();
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true),
eq(false), eq(false));
}
@Test
public void dataReadWithShouldAllowCompression() throws Exception {
when(local.allowCompressedData()).thenReturn(true);
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, false, false, true);
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
eq(false), eq(false), eq(true), any(Http2InboundFlowController.FrameWriter.class));
verify(stream, never()).closeRemoteSide();
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(false),
eq(false), eq(true));
}
@Test(expected = Http2Exception.class)
public void dataReadShouldDisallowCompression() throws Exception {
when(local.allowCompressedData()).thenReturn(false);
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, false, false, true);
eq(false));
}
@Test
@ -421,14 +421,12 @@ public class DelegatingHttp2ConnectionHandlerTest {
settings.pushEnabled(true);
settings.initialWindowSize(123);
settings.maxConcurrentStreams(456);
settings.allowCompressedData(true);
settings.maxHeaderTableSize(789);
settings.headerTableSize(789);
decode().onSettingsRead(ctx, settings);
verify(remote).allowPushTo(true);
verify(outboundFlow).initialOutboundWindowSize(123);
verify(local).maxStreams(456);
assertTrue(handler.settings().allowCompressedData());
verify(writer).maxHeaderTableSize(789);
verify(writer).maxHeaderTableSize(789L);
// Take into account the time this was called during setup().
verify(writer, times(2)).writeSettingsAck(eq(ctx), eq(promise));
verify(observer).onSettingsRead(eq(ctx), eq(settings));
@ -441,22 +439,6 @@ public class DelegatingHttp2ConnectionHandlerTest {
verify(observer).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER));
}
@Test(expected = Http2Exception.class)
public void serverAltSvcReadShouldThrow() throws Exception {
when(connection.isServer()).thenReturn(true);
decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, EMPTY_BUFFER, "www.example.com", null);
}
@Test
public void clientAltSvcReadShouldNotifyObserver() throws Exception {
String host = "www.host.com";
String origin = "www.origin.com";
when(connection.isServer()).thenReturn(false);
decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
verify(observer).onAltSvcRead(eq(ctx), eq(STREAM_ID), eq(1L), eq(2), eq(EMPTY_BUFFER),
eq(host), eq(origin));
}
@Test
public void dataWriteAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
@ -464,21 +446,6 @@ public class DelegatingHttp2ConnectionHandlerTest {
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
}
@Test
public void dataWriteShouldDisallowCompression() throws Exception {
when(local.allowCompressedData()).thenReturn(false);
ChannelFuture future = handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, true);
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
}
@Test
public void dataWriteShouldAllowCompression() throws Exception {
when(remote.allowCompressedData()).thenReturn(true);
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, true);
verify(outboundFlow).sendFlowControlled(eq(STREAM_ID), eq(dummyData()), eq(0), eq(false),
eq(false), eq(true), any(Http2OutboundFlowController.FrameWriter.class));
}
@Test
public void dataWriteShouldSucceed() throws Exception {
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, false);
@ -600,44 +567,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
@Test
public void settingsWriteShouldNotUpdateSettings() throws Exception {
Http2Settings settings = new Http2Settings();
settings.allowCompressedData(false);
settings.initialWindowSize(100);
settings.pushEnabled(false);
settings.maxConcurrentStreams(1000);
settings.maxHeaderTableSize(2000);
settings.headerTableSize(2000);
handler.writeSettings(ctx, promise, settings);
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
// Verify that application of local settings must not be done when it is dispatched.
verify(local, never()).allowCompressedData(eq(false));
verify(inboundFlow, never()).initialInboundWindowSize(eq(100));
verify(local, never()).allowPushTo(eq(false));
verify(remote, never()).maxStreams(eq(1000));
verify(reader, never()).maxHeaderTableSize(eq(2000));
verify(reader, never()).maxHeaderTableSize(eq(2000L));
// Verify that settings values are applied on the reception of SETTINGS ACK
decode().onSettingsAckRead(ctx);
verify(local).allowCompressedData(eq(false));
verify(inboundFlow).initialInboundWindowSize(eq(100));
verify(local).allowPushTo(eq(false));
verify(remote).maxStreams(eq(1000));
verify(reader).maxHeaderTableSize(eq(2000));
}
@Test
public void clientWriteAltSvcShouldThrow() throws Exception {
when(connection.isServer()).thenReturn(false);
ChannelFuture future = handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, EMPTY_BUFFER,
"www.example.com", null);
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
}
@Test
public void serverWriteAltSvcShouldSucceed() throws Exception {
String host = "www.host.com";
String origin = "www.origin.com";
when(connection.isServer()).thenReturn(true);
handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
verify(writer).writeAltSvc(eq(ctx), eq(promise), eq(STREAM_ID), eq(1L), eq(2),
eq(EMPTY_BUFFER), eq(host), eq(origin));
verify(reader).maxHeaderTableSize(eq(2000L));
}
private static ByteBuf dummyData() {

View File

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

View File

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

View File

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

View File

@ -247,6 +247,42 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
return outValues;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
for (Entry<V> entry : entries()) {
V value = entry.value();
int hash = value == null ? 0 : value.hashCode();
result = prime * result + hash;
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj || obj == null || getClass() != obj.getClass()) {
return true;
}
@SuppressWarnings("rawtypes")
IntObjectHashMap other = (IntObjectHashMap) obj;
if (size != other.size) {
return false;
}
for (Entry<V> entry : entries()) {
V value = entry.value();
Object otherValue = other.get(entry.key());
if (value == null) {
if (otherValue != null || !other.containsKey(entry.key())) {
return false;
}
} else if (!value.equals(otherValue)) {
return false;
}
}
return true;
}
/**
* Copies the occupied entries from the source to the target array.
*/

View File

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

View File

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