HTTP/2 Draft 16

Motivation:
HTTP/2 draft 16 has been released https://tools.ietf.org/html/draft-ietf-httpbis-http2-16.

Modifications:
The HTTP/2 codec should be updated to support draft 16.

Result:
HTTP/2 codec is draft 16 compliant.
This commit is contained in:
Scott Mitchell 2014-12-08 22:51:10 -05:00
parent 2cf4ee9322
commit 1293aba28e
24 changed files with 363 additions and 270 deletions

View File

@ -21,7 +21,9 @@ 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.immediateRemovalPolicy;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.REFUSED_STREAM;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.streamError;
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;
@ -153,13 +155,13 @@ public class DefaultHttp2Connection implements Http2Connection {
}
@Override
public Http2Stream createLocalStream(int streamId, boolean halfClosed) throws Http2Exception {
return local().createStream(streamId, halfClosed);
public Http2Stream createLocalStream(int streamId) throws Http2Exception {
return local().createStream(streamId);
}
@Override
public Http2Stream createRemoteStream(int streamId, boolean halfClosed) throws Http2Exception {
return remote().createStream(streamId, halfClosed);
public Http2Stream createRemoteStream(int streamId) throws Http2Exception {
return remote().createStream(streamId);
}
@Override
@ -194,14 +196,14 @@ public class DefaultHttp2Connection implements Http2Connection {
}
private void activate(DefaultStream stream) {
activeStreams.add(stream);
if (activeStreams.add(stream)) {
// Update the number of active streams initiated by the endpoint.
stream.createdBy().numActiveStreams++;
// Update the number of active streams initiated by the endpoint.
stream.createdBy().numActiveStreams++;
// Notify the listeners.
for (Listener listener : listeners) {
listener.streamActive(stream);
// Notify the listeners.
for (Listener listener : listeners) {
listener.streamActive(stream);
}
}
}
@ -364,9 +366,12 @@ public class DefaultHttp2Connection implements Http2Connection {
"Invalid weight: %d. Must be between %d and %d (inclusive).", weight, MIN_WEIGHT, MAX_WEIGHT));
}
// Get the parent stream.
DefaultStream newParent = (DefaultStream) requireStream(parentStreamId);
if (this == newParent) {
DefaultStream newParent = (DefaultStream) stream(parentStreamId);
if (newParent == null) {
// Streams can depend on other streams in the IDLE state. We must ensure
// the stream has been "created" in order to use it in the priority tree.
newParent = createdBy().createStream(parentStreamId);
} else if (this == newParent) {
throw new IllegalArgumentException("A stream cannot depend on itself");
}
@ -389,8 +394,11 @@ public class DefaultHttp2Connection implements Http2Connection {
}
@Override
public Http2Stream openForPush() throws Http2Exception {
public Http2Stream open(boolean halfClosed) throws Http2Exception {
switch (state) {
case IDLE:
state = halfClosed ? isLocal() ? HALF_CLOSED_LOCAL : HALF_CLOSED_REMOTE : OPEN;
break;
case RESERVED_LOCAL:
state = HALF_CLOSED_REMOTE;
break;
@ -398,8 +406,9 @@ public class DefaultHttp2Connection implements Http2Connection {
state = HALF_CLOSED_LOCAL;
break;
default:
throw connectionError(PROTOCOL_ERROR, "Attempting to open non-reserved stream for push");
throw streamError(id, PROTOCOL_ERROR, "Attempting to open a stream in an invalid state: " + state);
}
activate(this);
return this;
}
@ -419,14 +428,14 @@ public class DefaultHttp2Connection implements Http2Connection {
}
private void deactivate(DefaultStream stream) {
activeStreams.remove(stream);
if (activeStreams.remove(stream)) {
// Update the number of active streams initiated by the endpoint.
stream.createdBy().numActiveStreams--;
// Update the number of active streams initiated by the endpoint.
stream.createdBy().numActiveStreams--;
// Notify the listeners.
for (Listener listener : listeners) {
listener.streamInactive(stream);
// Notify the listeners.
for (Listener listener : listeners) {
listener.streamInactive(stream);
}
}
}
@ -482,6 +491,10 @@ public class DefaultHttp2Connection implements Http2Connection {
return localEndpoint.createdStreamId(id) ? localEndpoint : remoteEndpoint;
}
final boolean isLocal() {
return localEndpoint.createdStreamId(id);
}
final void weight(short weight) {
if (weight != this.weight) {
if (parent != null) {
@ -684,7 +697,7 @@ public class DefaultHttp2Connection implements Http2Connection {
}
@Override
public Http2Stream openForPush() {
public Http2Stream open(boolean halfClosed) {
throw new UnsupportedOperationException();
}
@ -758,24 +771,17 @@ public class DefaultHttp2Connection implements Http2Connection {
}
@Override
public DefaultStream createStream(int streamId, boolean halfClosed) throws Http2Exception {
public DefaultStream createStream(int streamId) throws Http2Exception {
checkNewStreamAllowed(streamId);
// Create and initialize the stream.
DefaultStream stream = new DefaultStream(streamId);
if (halfClosed) {
stream.state = isLocal() ? HALF_CLOSED_LOCAL : HALF_CLOSED_REMOTE;
} else {
stream.state = OPEN;
}
// Update the next and last stream IDs.
nextStreamId = streamId + 2;
lastStreamCreated = streamId;
// Register the stream and mark it as active.
addStream(stream);
activate(stream);
return stream;
}
@ -897,7 +903,7 @@ public class DefaultHttp2Connection implements Http2Connection {
}
verifyStreamId(streamId);
if (!acceptingNewStreams()) {
throw connectionError(PROTOCOL_ERROR, "Maximum streams exceeded for this endpoint.");
throw connectionError(REFUSED_STREAM, "Maximum streams exceeded for this endpoint.");
}
}

View File

@ -15,8 +15,8 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
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.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
@ -222,7 +222,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
// We should ignore this frame if RST_STREAM was sent or if GO_AWAY was sent with a
// lower stream ID.
boolean shouldApplyFlowControl = false;
boolean shouldIgnore = shouldIgnoreFrame(stream);
boolean shouldIgnore = shouldIgnoreFrame(stream, false);
Http2Exception error = null;
switch (stream.state()) {
case OPEN:
@ -322,21 +322,20 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
Http2Stream stream = connection.stream(streamId);
verifyGoAwayNotReceived();
verifyRstStreamNotReceived(stream);
if (connection.goAwaySent() || stream != null && shouldIgnoreFrame(stream)) {
if (shouldIgnoreFrame(stream, false)) {
// Ignore this frame.
return;
}
if (stream == null) {
stream = connection.createRemoteStream(streamId, endOfStream);
stream = connection.createRemoteStream(streamId).open(endOfStream);
} else {
verifyEndOfStreamNotReceived(stream);
switch (stream.state()) {
case RESERVED_REMOTE:
// Received headers for a reserved push stream ... open it for push to the
// local endpoint.
stream.openForPush();
case IDLE:
stream.open(endOfStream);
break;
case OPEN:
case HALF_CLOSED_LOCAL:
@ -371,16 +370,24 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
boolean exclusive) throws Http2Exception {
verifyPrefaceReceived();
Http2Stream stream = connection.requireStream(streamId);
Http2Stream stream = connection.stream(streamId);
verifyGoAwayNotReceived();
if (shouldIgnoreFrame(stream)) {
// Ignore frames for any stream created after we sent a go-away.
if (shouldIgnoreFrame(stream, true)) {
// Ignore this frame.
return;
}
listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
if (stream == null) {
// PRIORITY frames always identify a stream. This means that if a PRIORITY frame is the
// first frame to be received for a stream that we must create the stream.
stream = connection.createRemoteStream(streamId);
}
// This call will create a stream for streamDependency if necessary.
// For this reason it must be done before notifying the listener.
stream.setPriority(streamDependency, weight, exclusive);
listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
}
@Override
@ -497,7 +504,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
Http2Stream parentStream = connection.requireStream(streamId);
verifyGoAwayNotReceived();
verifyRstStreamNotReceived(parentStream);
if (shouldIgnoreFrame(parentStream)) {
if (shouldIgnoreFrame(parentStream, false)) {
// Ignore frames for any stream created after we sent a go-away.
return;
}
@ -525,7 +532,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
Http2Stream stream = connection.requireStream(streamId);
verifyGoAwayNotReceived();
verifyRstStreamNotReceived(stream);
if (stream.state() == CLOSED || shouldIgnoreFrame(stream)) {
if (stream.state() == CLOSED || shouldIgnoreFrame(stream, false)) {
// Ignore frames for any stream created after we sent a go-away.
return;
}
@ -546,15 +553,16 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
* Indicates whether or not frames for the given stream should be ignored based on the state of the
* stream/connection.
*/
private boolean shouldIgnoreFrame(Http2Stream stream) {
if (connection.goAwaySent() && connection.remote().lastStreamCreated() <= stream.id()) {
private boolean shouldIgnoreFrame(Http2Stream stream, boolean allowResetSent) {
if (connection.goAwaySent() &&
(stream == null || connection.remote().lastStreamCreated() <= stream.id())) {
// Frames from streams created after we sent a go-away should be ignored.
// Frames for the connection stream ID (i.e. 0) will always be allowed.
return true;
}
// Also ignore inbound frames after we sent a RST_STREAM frame.
return stream.isResetSent();
return stream != null && !allowResetSent && stream.isResetSent();
}
/**

View File

@ -65,8 +65,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
}
@Override
public Builder frameWriter(
Http2FrameWriter frameWriter) {
public Builder frameWriter(Http2FrameWriter frameWriter) {
this.frameWriter = frameWriter;
return this;
}
@ -220,8 +219,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
}
if (stream == null) {
// Create a new locally-initiated stream.
stream = connection.createLocalStream(streamId, endOfStream);
stream = connection.createLocalStream(streamId).open(endOfStream);
} else {
if (stream.isResetSent()) {
throw new IllegalStateException("Sending headers after sending RST_STREAM.");
@ -233,8 +231,8 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
// An existing stream...
switch (stream.state()) {
case RESERVED_LOCAL:
// Sending headers on a reserved push stream ... open it for push to the remote endpoint.
stream.openForPush();
case IDLE:
stream.open(endOfStream);
break;
case OPEN:
case HALF_CLOSED_REMOTE:
@ -316,14 +314,17 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
}
// Update the priority on this stream.
connection.requireStream(streamId).setPriority(streamDependency, weight, exclusive);
Http2Stream stream = connection.stream(streamId);
if (stream == null) {
stream = connection.createLocalStream(streamId);
}
stream.setPriority(streamDependency, weight, exclusive);
} catch (Throwable e) {
return promise.setFailure(e);
}
ChannelFuture future =
frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive,
promise);
ChannelFuture future = frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise);
ctx.flush();
return future;
}
@ -425,10 +426,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
return promise.setFailure(e);
}
// Write the frame.
ChannelFuture future =
frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding,
promise);
ChannelFuture future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise);
ctx.flush();
return future;
}

View File

@ -33,7 +33,7 @@ public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2He
* <p>
*
* <strong>Note</strong> that setting {@code forceKeyToLower} to {@code false} can violate the
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-15#section-8.1.2">HTTP/2 specification</a>
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2">HTTP/2 specification</a>
* which specifies that a request or response containing an uppercase header field MUST be treated
* as malformed. Only set {@code forceKeyToLower} to {@code false} if you are explicitly using lowercase
* header field names and want to avoid the conversion to lowercase.

View File

@ -59,13 +59,20 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
// Add a flow state for the connection.
final Http2Stream connectionStream = connection.connectionStream();
connectionStream.setProperty(FlowState.class, new FlowState(connectionStream));
connectionStream.setProperty(FlowState.class, new FlowState(connectionStream, initialWindowSize));
// Register for notification of new streams.
connection.addListener(new Http2ConnectionAdapter() {
@Override
public void streamAdded(Http2Stream stream) {
stream.setProperty(FlowState.class, new FlowState(stream));
stream.setProperty(FlowState.class, new FlowState(stream, 0));
}
@Override
public void streamActive(Http2Stream stream) {
// Need to be sure the stream's initial window is adjusted for SETTINGS
// frames which may have been exchanged while it was in IDLE
state(stream).window(initialWindowSize);
}
});
}
@ -241,9 +248,9 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
private int lowerBound;
private boolean endOfStream;
FlowState(Http2Stream stream) {
FlowState(Http2Stream stream, int initialWindowSize) {
this.stream = stream;
window = processedWindow = initialStreamWindowSize = initialWindowSize;
window(initialWindowSize);
streamWindowUpdateRatio = windowUpdateRatio;
}
@ -251,6 +258,10 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
return window;
}
void window(int initialWindowSize) {
window = processedWindow = initialStreamWindowSize = initialWindowSize;
}
void endOfStream(boolean endOfStream) {
this.endOfStream = endOfStream;
}
@ -343,7 +354,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
writeWindowUpdateIfNeeded(ctx);
}
public int unconsumedBytes() {
int unconsumedBytes() {
return processedWindow - window;
}

View File

@ -61,14 +61,22 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
this.frameWriter = checkNotNull(frameWriter, "frameWriter");
// Add a flow state for the connection.
connection.connectionStream().setProperty(FlowState.class, new FlowState(connection.connectionStream()));
connection.connectionStream().setProperty(FlowState.class,
new FlowState(connection.connectionStream(), initialWindowSize));
// Register for notification of new streams.
connection.addListener(new Http2ConnectionAdapter() {
@Override
public void streamAdded(Http2Stream stream) {
// Just add a new flow state to the stream.
stream.setProperty(FlowState.class, new FlowState(stream));
stream.setProperty(FlowState.class, new FlowState(stream, 0));
}
@Override
public void streamActive(Http2Stream stream) {
// Need to be sure the stream's initial window is adjusted for SETTINGS
// frames which may have been exchanged while it was in IDLE
state(stream).window(initialWindowSize);
}
@Override
@ -91,7 +99,10 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
public void priorityTreeParentChanged(Http2Stream stream, Http2Stream oldParent) {
Http2Stream parent = stream.parent();
if (parent != null) {
state(parent).incrementStreamableBytesForTree(state(stream).streamableBytesForTree());
int delta = state(stream).streamableBytesForTree();
if (delta != 0) {
state(parent).incrementStreamableBytesForTree(delta);
}
}
}
@ -99,7 +110,10 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
public void priorityTreeParentChanging(Http2Stream stream, Http2Stream newParent) {
Http2Stream parent = stream.parent();
if (parent != null) {
state(parent).incrementStreamableBytesForTree(-state(stream).streamableBytesForTree());
int delta = -state(stream).streamableBytesForTree();
if (delta != 0) {
state(parent).incrementStreamableBytesForTree(delta);
}
}
}
});
@ -355,46 +369,51 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
final class FlowState {
private final Queue<Frame> pendingWriteQueue;
private final Http2Stream stream;
private int window = initialWindowSize;
private int window;
private int pendingBytes;
private int streamableBytesForTree;
private int allocated;
private ChannelFuture lastNewFrame;
FlowState(Http2Stream stream) {
FlowState(Http2Stream stream, int initialWindowSize) {
this.stream = stream;
window(initialWindowSize);
pendingWriteQueue = new ArrayDeque<Frame>(2);
}
public int window() {
int window() {
return window;
}
void window(int initialWindowSize) {
window = initialWindowSize;
}
/**
* Increment the number of bytes allocated to this stream by the priority algorithm
*/
private void allocate(int bytes) {
void allocate(int bytes) {
allocated += bytes;
}
/**
* Gets the number of bytes that have been allocated to this stream by the priority algorithm.
*/
private int allocated() {
int allocated() {
return allocated;
}
/**
* Reset the number of bytes that have been allocated to this stream by the priority algorithm.
*/
private void resetAllocated() {
void resetAllocated() {
allocated = 0;
}
/**
* Increments the flow control window for this stream by the given delta and returns the new value.
*/
private int incrementStreamWindow(int delta) throws Http2Exception {
int incrementStreamWindow(int delta) throws Http2Exception {
if (delta > 0 && Integer.MAX_VALUE - delta < window) {
throw streamError(stream.id(), FLOW_CONTROL_ERROR,
"Window size overflow for stream: %d", stream.id());
@ -404,7 +423,9 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
// Update this branch of the priority tree if the streamable bytes have changed for this node.
int streamableDelta = streamableBytes() - previouslyStreamable;
incrementStreamableBytesForTree(streamableDelta);
if (streamableDelta != 0) {
incrementStreamableBytesForTree(streamableDelta);
}
return window;
}
@ -439,7 +460,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
/**
* Creates a new frame with the given values but does not add it to the pending queue.
*/
private Frame newFrame(final ChannelPromise promise, ByteBuf data, int padding, boolean endStream) {
Frame newFrame(final ChannelPromise promise, ByteBuf data, int padding, boolean endStream) {
// Store this as the future for the most recent write attempt.
lastNewFrame = promise;
return new Frame(new SimplePromiseAggregator(promise), data, padding, endStream);
@ -455,14 +476,14 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
/**
* Returns the the head of the pending queue, or {@code null} if empty.
*/
private Frame peek() {
Frame peek() {
return pendingWriteQueue.peek();
}
/**
* Clears the pending queue and writes errors for each remaining frame.
*/
private void clear() {
void clear() {
for (;;) {
Frame frame = pendingWriteQueue.poll();
if (frame == null) {
@ -478,7 +499,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
* the number of pending writes available, or because a frame does not support splitting on arbitrary
* boundaries.
*/
private int writeBytes(int bytes) {
int writeBytes(int bytes) {
if (!stream.localSideOpen()) {
return 0;
}
@ -513,12 +534,10 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
* Recursively increments the streamable bytes for this branch in the priority tree starting at the current
* node.
*/
private void incrementStreamableBytesForTree(int numBytes) {
if (numBytes != 0) {
streamableBytesForTree += numBytes;
if (!stream.isRoot()) {
state(stream.parent()).incrementStreamableBytesForTree(numBytes);
}
void incrementStreamableBytesForTree(int numBytes) {
streamableBytesForTree += numBytes;
if (!stream.isRoot()) {
state(stream.parent()).incrementStreamableBytesForTree(numBytes);
}
}
@ -569,7 +588,9 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
pendingBytes += numBytes;
int delta = streamableBytes() - previouslyStreamable;
incrementStreamableBytesForTree(delta);
if (delta != 0) {
incrementStreamableBytesForTree(delta);
}
}
/**

View File

@ -34,10 +34,8 @@ 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";
// Draft 15 is actually supported but because draft 15 and draft 14 are binary compatible draft 14 is advertised
// for interoperability with technologies that have not yet updated, or are also advertising the older draft.
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-14";
public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-14";
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-16";
public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-16";
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
public static final short MAX_UNSIGNED_BYTE = 0xFF;
@ -57,7 +55,7 @@ public final class Http2CodecUtil {
public static final int SETTINGS_MAX_FRAME_SIZE = 5;
public static final int SETTINGS_MAX_HEADER_LIST_SIZE = 6;
public static final long MAX_HEADER_TABLE_SIZE = MAX_UNSIGNED_INT;
public static final int MAX_HEADER_TABLE_SIZE = Integer.MAX_VALUE; // Size limited by HPACK library
public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
@ -80,16 +78,14 @@ public final class Http2CodecUtil {
* Indicates whether or not the given value for max frame size falls within the valid range.
*/
public static boolean isMaxFrameSizeValid(int maxFrameSize) {
return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND
&& maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
}
/**
* Returns a buffer containing the the {@link #CONNECTION_PREFACE}.
*/
public static ByteBuf connectionPrefaceBuf() {
// Return a duplicate so that modifications to the reader index will not affect the original
// buffer.
// Return a duplicate so that modifications to the reader index will not affect the original buffer.
return Unpooled.wrappedBuffer(CONNECTION_PREFACE);
}
@ -97,8 +93,7 @@ public final class Http2CodecUtil {
* Returns a buffer filled with all zeros that is the appropriate length for a PING frame.
*/
public static ByteBuf emptyPingBuf() {
// Return a duplicate so that modifications to the reader index will not affect the original
// buffer.
// Return a duplicate so that modifications to the reader index will not affect the original buffer.
return Unpooled.wrappedBuffer(EMPTY_PING);
}
@ -204,6 +199,5 @@ public final class Http2CodecUtil {
throw cause;
}
private Http2CodecUtil() {
}
private Http2CodecUtil() { }
}

View File

@ -114,29 +114,32 @@ public interface Http2Connection {
/**
* Creates a stream initiated by this endpoint. This could fail for the following reasons:
* <p/>
* - The requested stream ID is not the next sequential ID for this endpoint. <br>
* - The stream already exists. <br>
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
* - The connection is marked as going away}. <br>
*
* <ul>
* <li>The requested stream ID is not the next sequential ID for this endpoint.</li>
* <li>The stream already exists.</li>
* <li>The number of concurrent streams is above the allowed threshold for this endpoint.</li>
* <li>The connection is marked as going away.</li>
* </ul>
* <p>
* The caller is expected to {@link Http2Stream#open()} the stream.
* @param streamId The ID of the stream
* @param halfClosed if true, the stream is created in the half-closed state with respect to
* this endpoint. Otherwise it's created in the open state.
* @see Http2Stream#open()
* @see Http2Stream#open(boolean)
*/
Http2Stream createStream(int streamId, boolean halfClosed) throws Http2Exception;
Http2Stream createStream(int streamId) throws Http2Exception;
/**
* Creates a push stream in the reserved state for this endpoint and notifies all listeners.
* This could fail for the following reasons:
* <p/>
* - Server push is not allowed to the opposite endpoint. <br>
* - The requested stream ID is not the next sequential stream ID for this endpoint. <br>
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
* - The connection is marked as going away. <br>
* - The parent stream ID does not exist or is not open from the side sending the push
* promise. <br>
* - Could not set a valid priority for the new stream.
* <ul>
* <li>Server push is not allowed to the opposite endpoint.</li>
* <li>The requested stream ID is not the next sequential stream ID for this endpoint.</li>
* <li>The number of concurrent streams is above the allowed threshold for this endpoint.</li>
* <li>The connection is marked as going away.</li>
* <li>The parent stream ID does not exist or is not open from the side sending the push
* promise.</li>
* <li>Could not set a valid priority for the new stream.</li>
* </ul>
*
* @param streamId the ID of the push stream
* @param parent the parent stream used to initiate the push stream.
@ -250,9 +253,10 @@ public interface Http2Connection {
Endpoint<Http2LocalFlowController> local();
/**
* Creates a new stream initiated by the local endpoint. See {@link Endpoint#createStream(int, boolean)}.
* Creates a new stream initiated by the local endpoint
* @see Endpoint#createStream(int)
*/
Http2Stream createLocalStream(int streamId, boolean halfClosed) throws Http2Exception;
Http2Stream createLocalStream(int streamId) throws Http2Exception;
/**
* Gets a view of this connection from the remote {@link Endpoint}.
@ -260,9 +264,10 @@ public interface Http2Connection {
Endpoint<Http2RemoteFlowController> remote();
/**
* Creates a new stream initiated by the remote endpoint. See {@link Endpoint#createStream(int, boolean)}.
* Creates a new stream initiated by the remote endpoint.
* @see Endpoint#createStream(int)
*/
Http2Stream createRemoteStream(int streamId, boolean halfClosed) throws Http2Exception;
Http2Stream createRemoteStream(int streamId) throws Http2Exception;
/**
* Indicates whether or not a {@code GOAWAY} was received from the remote endpoint.

View File

@ -123,7 +123,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
}
// Create a local stream used for the HTTP cleartext upgrade.
connection().createLocalStream(HTTP_UPGRADE_STREAM_ID, true);
connection().createLocalStream(HTTP_UPGRADE_STREAM_ID).open(true);
}
/**
@ -142,7 +142,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
encoder.remoteSettings(settings);
// Create a stream in the half-closed state.
connection().createRemoteStream(HTTP_UPGRADE_STREAM_ID, true);
connection().createRemoteStream(HTTP_UPGRADE_STREAM_ID).open(true);
}
@Override

View File

@ -30,7 +30,7 @@ public final class Http2SecurityUtil {
* Ciphers</a> and <a
* href="https://wiki.mozilla.org/Security/Server_Side_TLS#Non-Backward_Compatible_Ciphersuite">Mozilla Cipher
* Suites</a> in accordance with the <a
* href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-9.2.2">HTTP/2 Specification</a>.
* href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-9.2.2">HTTP/2 Specification</a>.
*/
public static final List<String> CIPHERS;
@ -40,7 +40,6 @@ public final class Http2SecurityUtil {
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDHE-ECDSA-AES256-GCM-SHA384 */
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", /* openssl = ECDHE-ECDSA-AES128-GCM-SHA256 */
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDHE-RSA-AES256-GCM-SHA384 */
"TLS_RSA_WITH_AES_256_GCM_SHA384", /* openssl = AES256-GCM-SHA384 */
/* REQUIRED BY HTTP/2 SPEC */
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", /* openssl = ECDHE-RSA-AES128-GCM-SHA256 */
/* REQUIRED BY HTTP/2 SPEC */
@ -50,25 +49,14 @@ public final class Http2SecurityUtil {
private static final List<String> CIPHERS_JAVA_NO_MOZILLA_INCREASED_SECURITY = Collections.unmodifiableList(Arrays
.asList(
/* Java 8 */
"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDH-ECDSA-AES256-GCM-SHA384 */
"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDH-RSA-AES256-GCM-SHA384 */
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", /* openssl = DHE-RSA-AES256-GCM-SHA384 */
"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", /* openssl = DHE-DSS-AES256-GCM-SHA384 */
"TLS_RSA_WITH_AES_128_GCM_SHA256", /* openssl = AES128-GCM-SHA256 */
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", /* openssl = ECDH-ECDSA-AES128-GCM-SHA256 */
"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" /* openssl = ECDH-RSA-AES128-GCM-SHA256 */));
private static final List<String> CIPHERS_JAVA_DISABLED_DEFAULT = Collections.unmodifiableList(Arrays.asList(
/* Java 8 */
"TLS_DH_anon_WITH_AES_256_GCM_SHA384", /* openssl = ADH-AES256-GCM-SHA384 */
"TLS_DH_anon_WITH_AES_128_GCM_SHA256" /* openssl = ADH-AES128-GCM-SHA256 */));
"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" /* openssl = DHE-DSS-AES256-GCM-SHA384 */));
static {
List<String> ciphers = new ArrayList<String>(CIPHERS_JAVA_MOZILLA_INCREASED_SECURITY.size()
+ CIPHERS_JAVA_NO_MOZILLA_INCREASED_SECURITY.size() + CIPHERS_JAVA_DISABLED_DEFAULT.size());
+ CIPHERS_JAVA_NO_MOZILLA_INCREASED_SECURITY.size());
ciphers.addAll(CIPHERS_JAVA_MOZILLA_INCREASED_SECURITY);
ciphers.addAll(CIPHERS_JAVA_NO_MOZILLA_INCREASED_SECURITY);
ciphers.addAll(CIPHERS_JAVA_DISABLED_DEFAULT);
CIPHERS = Collections.unmodifiableList(ciphers);
}

View File

@ -75,8 +75,8 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
*
* @throws IllegalArgumentException if verification of the setting fails.
*/
public Http2Settings headerTableSize(long value) {
put(SETTINGS_HEADER_TABLE_SIZE, value);
public Http2Settings headerTableSize(int value) {
put(SETTINGS_HEADER_TABLE_SIZE, (long) value);
return this;
}
@ -189,8 +189,7 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
switch (key) {
case SETTINGS_HEADER_TABLE_SIZE:
if (value < MIN_HEADER_TABLE_SIZE || value > MAX_HEADER_TABLE_SIZE) {
throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: "
+ value);
throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: " + value);
}
break;
case SETTINGS_ENABLE_PUSH:
@ -212,14 +211,12 @@ public final class Http2Settings extends IntObjectHashMap<Long> {
break;
case SETTINGS_MAX_FRAME_SIZE:
if (!isMaxFrameSizeValid(value.intValue())) {
throw new IllegalArgumentException("Setting MAX_FRAME_SIZE is invalid: "
+ value);
throw new IllegalArgumentException("Setting MAX_FRAME_SIZE is invalid: " + value);
}
break;
case SETTINGS_MAX_HEADER_LIST_SIZE:
if (value < MIN_HEADER_LIST_SIZE || value > MAX_HEADER_LIST_SIZE) {
throw new IllegalArgumentException("Setting MAX_HEADER_LIST_SIZE is invalid: "
+ value);
throw new IllegalArgumentException("Setting MAX_HEADER_LIST_SIZE is invalid: " + value);
}
break;
default:

View File

@ -46,9 +46,18 @@ public interface Http2Stream {
State state();
/**
* If this is a reserved push stream, opens the stream for push in one direction.
* Add this stream to {@link Http2Connection#activeStreams()} and transition state to:
* <ul>
* <li>{@link State#OPEN} if {@link #state()} is {@link State#IDLE} and {@code halfClosed} is {@code false}.</li>
* <li>{@link State#HALF_CLOSED_LOCAL} if {@link #state()} is {@link State#IDLE} and {@code halfClosed}
* is {@code true} and the stream is local.</li>
* <li>{@link State#HALF_CLOSED_REMOTE} if {@link #state()} is {@link State#IDLE} and {@code halfClosed}
* is {@code true} and the stream is remote.</li>
* <li>{@link State#RESERVED_LOCAL} if {@link #state()} is {@link State#HALF_CLOSED_REMOTE}.</li>
* <li>{@link State#RESERVED_REMOTE} if {@link #state()} is {@link State#HALF_CLOSED_LOCAL}.</li>
* </ul>
*/
Http2Stream openForPush() throws Http2Exception;
Http2Stream open(boolean halfClosed) throws Http2Exception;
/**
* Closes the stream.
@ -160,8 +169,7 @@ public interface Http2Stream {
* This only applies if the stream has a parent.
* @return this stream.
*/
Http2Stream setPriority(int parentStreamId, short weight, boolean exclusive)
throws Http2Exception;
Http2Stream setPriority(int parentStreamId, short weight, boolean exclusive) throws Http2Exception;
/**
* Indicates whether or not this stream is the root node of the priority tree.

View File

@ -28,6 +28,7 @@ import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
@ -59,6 +60,7 @@ public final class HttpUtil {
add(HttpHeaderNames.PROXY_CONNECTION);
add(HttpHeaderNames.TRANSFER_ENCODING);
add(HttpHeaderNames.HOST);
add(HttpHeaderNames.UPGRADE);
add(ExtensionHeaderNames.STREAM_ID.text());
add(ExtensionHeaderNames.AUTHORITY.text());
add(ExtensionHeaderNames.SCHEME.text());
@ -68,19 +70,19 @@ public final class HttpUtil {
/**
* This will be the method used for {@link HttpRequest} objects generated out of the HTTP message flow defined in <a
* href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>
* href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.">HTTP/2 Spec Message Flow</a>
*/
public static final HttpMethod OUT_OF_MESSAGE_SEQUENCE_METHOD = HttpMethod.OPTIONS;
/**
* This will be the path used for {@link HttpRequest} objects generated out of the HTTP message flow defined in <a
* href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>
* href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.">HTTP/2 Spec Message Flow</a>
*/
public static final String OUT_OF_MESSAGE_SEQUENCE_PATH = "";
/**
* This will be the status code used for {@link HttpResponse} objects generated out of the HTTP message flow defined
* in <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>
* in <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.">HTTP/2 Spec Message Flow</a>
*/
public static final HttpResponseStatus OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE = HttpResponseStatus.OK;
@ -308,7 +310,12 @@ public final class HttpUtil {
final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase();
if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName)) {
AsciiString aValue = AsciiString.of(entry.getValue());
out.add(aName, aValue);
// https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2
// makes a special exception for TE
if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) ||
aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) {
out.add(aName, aValue);
}
}
return true;
}
@ -364,7 +371,7 @@ public final class HttpUtil {
translatedName = name;
}
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.2.3
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.3
// All headers that start with ':' are only valid in HTTP/2 context
if (translatedName.isEmpty() || translatedName.charAt(0) == ':') {
throw streamError(streamId, PROTOCOL_ERROR,

View File

@ -32,7 +32,7 @@ import static io.netty.util.internal.ObjectUtil.*;
/**
* This adapter provides just header/data events from the HTTP message flow defined
* here <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>.
* here <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.">HTTP/2 Spec Message Flow</a>.
* <p>
* See {@link HttpToHttp2ConnectionHandler} to get translation from HTTP/1.x objects to HTTP/2 frames for writes.
*/

View File

@ -171,7 +171,7 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA
FullHttpMessage msg = messageMap.get(stream.id());
if (msg == null) {
// msg may be null if a HTTP/2 frame event in received outside the HTTP message flow
// For example a PRIORITY frame can be received in any state besides IDLE
// For example a PRIORITY frame can be received in any state
// and the HTTP message flow exists in OPEN.
if (parent != null && !parent.equals(connection.connectionStream())) {
HttpHeaders headers = new DefaultHttpHeaders();
@ -192,10 +192,10 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA
@Override
public void onWeightChanged(Http2Stream stream, short oldWeight) {
FullHttpMessage msg = messageMap.get(stream.id());
HttpHeaders headers;
final HttpHeaders headers;
if (msg == null) {
// msg may be null if a HTTP/2 frame event in received outside the HTTP message flow
// For example a PRIORITY frame can be received in any state besides IDLE
// For example a PRIORITY frame can be received in any state
// and the HTTP message flow exists in OPEN.
headers = new DefaultHttpHeaders();
importOutOfMessageFlowHeaders(stream.id(), headers);

View File

@ -119,6 +119,7 @@ public class DefaultHttp2ConnectionDecoderTest {
when(channel.isActive()).thenReturn(true);
when(stream.id()).thenReturn(STREAM_ID);
when(stream.state()).thenReturn(OPEN);
when(stream.open(anyBoolean())).thenReturn(stream);
when(pushStream.id()).thenReturn(PUSH_STREAM_ID);
when(connection.activeStreams()).thenReturn(Collections.singletonList(stream));
when(connection.stream(STREAM_ID)).thenReturn(stream);
@ -131,19 +132,19 @@ public class DefaultHttp2ConnectionDecoderTest {
@Override
public Http2Stream answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return local.createStream((Integer) args[0], (Boolean) args[1]);
return local.createStream((Integer) args[0]);
}
}).when(connection).createLocalStream(anyInt(), anyBoolean());
}).when(connection).createLocalStream(anyInt());
doAnswer(new Answer<Http2Stream>() {
@Override
public Http2Stream answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return remote.createStream((Integer) args[0], (Boolean) args[1]);
return remote.createStream((Integer) args[0]);
}
}).when(connection).createRemoteStream(anyInt(), anyBoolean());
when(local.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream);
}).when(connection).createRemoteStream(anyInt());
when(local.createStream(eq(STREAM_ID))).thenReturn(stream);
when(local.reservePushStream(eq(PUSH_STREAM_ID), eq(stream))).thenReturn(pushStream);
when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream);
when(remote.createStream(eq(STREAM_ID))).thenReturn(stream);
when(remote.reservePushStream(eq(PUSH_STREAM_ID), eq(stream))).thenReturn(pushStream);
when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel);
@ -287,28 +288,34 @@ public class DefaultHttp2ConnectionDecoderTest {
public void headersReadAfterGoAwayShouldBeIgnored() throws Exception {
when(connection.goAwaySent()).thenReturn(true);
decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
verify(remote, never()).createStream(eq(STREAM_ID), eq(false));
verify(remote, never()).createStream(eq(STREAM_ID));
verify(stream, never()).open(anyBoolean());
// Verify that the event was absorbed and not propagated to the oberver.
verify(listener, never()).onHeadersRead(eq(ctx), anyInt(), any(Http2Headers.class), anyInt(), anyBoolean());
verify(remote, never()).createStream(anyInt(), anyBoolean());
verify(remote, never()).createStream(anyInt());
verify(stream, never()).open(anyBoolean());
}
@Test
public void headersReadForUnknownStreamShouldCreateStream() throws Exception {
when(remote.createStream(eq(5), eq(false))).thenReturn(stream);
decode().onHeadersRead(ctx, 5, EmptyHttp2Headers.INSTANCE, 0, false);
verify(remote).createStream(eq(5), eq(false));
verify(listener).onHeadersRead(eq(ctx), eq(5), eq(EmptyHttp2Headers.INSTANCE), eq(0),
final int streamId = 5;
when(remote.createStream(eq(streamId))).thenReturn(stream);
decode().onHeadersRead(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
verify(remote).createStream(eq(streamId));
verify(stream).open(eq(false));
verify(listener).onHeadersRead(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false));
}
@Test
public void headersReadForUnknownStreamShouldCreateHalfClosedStream() throws Exception {
when(remote.createStream(eq(5), eq(true))).thenReturn(stream);
decode().onHeadersRead(ctx, 5, EmptyHttp2Headers.INSTANCE, 0, true);
verify(remote).createStream(eq(5), eq(true));
verify(listener).onHeadersRead(eq(ctx), eq(5), eq(EmptyHttp2Headers.INSTANCE), eq(0),
final int streamId = 5;
when(remote.createStream(eq(streamId))).thenReturn(stream);
decode().onHeadersRead(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true);
verify(remote).createStream(eq(streamId));
verify(stream).open(eq(true));
verify(listener).onHeadersRead(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true));
}
@ -316,7 +323,7 @@ public class DefaultHttp2ConnectionDecoderTest {
public void headersReadForPromisedStreamShouldHalfOpenStream() throws Exception {
when(stream.state()).thenReturn(RESERVED_REMOTE);
decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
verify(stream).openForPush();
verify(stream).open(false);
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false));
}
@ -325,7 +332,7 @@ public class DefaultHttp2ConnectionDecoderTest {
public void headersReadForPromisedStreamShouldCloseStream() throws Exception {
when(stream.state()).thenReturn(RESERVED_REMOTE);
decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
verify(stream).openForPush();
verify(stream).open(true);
verify(lifecycleManager).closeRemoteSide(eq(stream), eq(future));
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true));
@ -357,9 +364,13 @@ public class DefaultHttp2ConnectionDecoderTest {
@Test
public void priorityReadShouldSucceed() throws Exception {
when(connection.stream(STREAM_ID)).thenReturn(null);
when(connection.requireStream(STREAM_ID)).thenReturn(null);
decode().onPriorityRead(ctx, STREAM_ID, 0, (short) 255, true);
verify(stream).setPriority(eq(0), eq((short) 255), eq(true));
verify(listener).onPriorityRead(eq(ctx), eq(STREAM_ID), eq(0), eq((short) 255), eq(true));
verify(connection).createRemoteStream(STREAM_ID);
verify(stream, never()).open(anyBoolean());
}
@Test

View File

@ -110,6 +110,7 @@ public class DefaultHttp2ConnectionEncoderTest {
when(channel.isActive()).thenReturn(true);
when(stream.id()).thenReturn(STREAM_ID);
when(stream.state()).thenReturn(OPEN);
when(stream.open(anyBoolean())).thenReturn(stream);
when(pushStream.id()).thenReturn(PUSH_STREAM_ID);
when(connection.activeStreams()).thenReturn(Collections.singletonList(stream));
when(connection.stream(STREAM_ID)).thenReturn(stream);
@ -121,19 +122,19 @@ public class DefaultHttp2ConnectionEncoderTest {
@Override
public Http2Stream answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return local.createStream((Integer) args[0], (Boolean) args[1]);
return local.createStream((Integer) args[0]);
}
}).when(connection).createLocalStream(anyInt(), anyBoolean());
}).when(connection).createLocalStream(anyInt());
doAnswer(new Answer<Http2Stream>() {
@Override
public Http2Stream answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return remote.createStream((Integer) args[0], (Boolean) args[1]);
return remote.createStream((Integer) args[0]);
}
}).when(connection).createRemoteStream(anyInt(), anyBoolean());
when(local.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream);
}).when(connection).createRemoteStream(anyInt());
when(local.createStream(eq(STREAM_ID))).thenReturn(stream);
when(local.reservePushStream(eq(PUSH_STREAM_ID), eq(stream))).thenReturn(pushStream);
when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream);
when(remote.createStream(eq(STREAM_ID))).thenReturn(stream);
when(remote.reservePushStream(eq(PUSH_STREAM_ID), eq(stream))).thenReturn(pushStream);
when(writer.writeSettings(eq(ctx), any(Http2Settings.class), eq(promise))).thenReturn(future);
when(writer.writeGoAway(eq(ctx), anyInt(), anyInt(), any(ByteBuf.class), eq(promise)))
@ -199,7 +200,8 @@ public class DefaultHttp2ConnectionEncoderTest {
when(connection.isGoAway()).thenReturn(true);
ChannelFuture future = encoder.writeHeaders(
ctx, 5, EmptyHttp2Headers.INSTANCE, 0, (short) 255, false, 0, false, promise);
verify(local, never()).createStream(anyInt(), anyBoolean());
verify(local, never()).createStream(anyInt());
verify(stream, never()).open(anyBoolean());
verify(writer, never()).writeHeaders(eq(ctx), anyInt(), any(Http2Headers.class), anyInt(), anyBoolean(),
eq(promise));
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
@ -210,9 +212,10 @@ public class DefaultHttp2ConnectionEncoderTest {
int streamId = 5;
when(stream.id()).thenReturn(streamId);
mockFutureAddListener(true);
when(local.createStream(eq(streamId), eq(false))).thenReturn(stream);
when(local.createStream(eq(streamId))).thenReturn(stream);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
verify(local).createStream(eq(streamId), eq(false));
verify(local).createStream(eq(streamId));
verify(stream).open(eq(false));
verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise));
}
@ -220,11 +223,12 @@ public class DefaultHttp2ConnectionEncoderTest {
@Test
public void headersWriteShouldCreateHalfClosedStream() throws Exception {
int streamId = 5;
when(stream.id()).thenReturn(5);
when(stream.id()).thenReturn(streamId);
mockFutureAddListener(true);
when(local.createStream(eq(streamId), eq(true))).thenReturn(stream);
when(local.createStream(eq(streamId))).thenReturn(stream);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true, promise);
verify(local).createStream(eq(streamId), eq(true));
verify(local).createStream(eq(streamId));
verify(stream).open(eq(true));
verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(promise));
}
@ -258,7 +262,7 @@ public class DefaultHttp2ConnectionEncoderTest {
mockFutureAddListener(true);
when(stream.state()).thenReturn(RESERVED_LOCAL);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false, promise);
verify(stream).openForPush();
verify(stream).open(false);
verify(stream, never()).closeLocalSide();
verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise));
@ -269,7 +273,7 @@ public class DefaultHttp2ConnectionEncoderTest {
mockFutureAddListener(true);
when(stream.state()).thenReturn(RESERVED_LOCAL).thenReturn(HALF_CLOSED_LOCAL);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, promise);
verify(stream).openForPush();
verify(stream).open(true);
verify(lifecycleManager).closeLocalSide(eq(stream), eq(promise));
verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(promise));
@ -301,9 +305,13 @@ public class DefaultHttp2ConnectionEncoderTest {
@Test
public void priorityWriteShouldSetPriorityForStream() throws Exception {
when(connection.stream(STREAM_ID)).thenReturn(null);
when(connection.requireStream(STREAM_ID)).thenReturn(null);
encoder.writePriority(ctx, STREAM_ID, 0, (short) 255, true, promise);
verify(stream).setPriority(eq(0), eq((short) 255), eq(true));
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq((short) 255), eq(true), eq(promise));
verify(connection).createLocalStream(STREAM_ID);
verify(stream, never()).open(anyBoolean());
}
@Test

View File

@ -15,6 +15,7 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -29,6 +30,7 @@ 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 io.netty.handler.codec.http2.Http2Connection.Endpoint;
import io.netty.handler.codec.http2.Http2Stream.State;
import java.util.Arrays;
@ -72,25 +74,25 @@ public class DefaultHttp2ConnectionTest {
@Test
public void serverCreateStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.local().createStream(2, false);
Http2Stream stream = server.local().createStream(2).open(false);
assertEquals(2, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(1, server.activeStreams().size());
assertEquals(2, server.local().lastStreamCreated());
stream = server.local().createStream(4, true);
stream = server.local().createStream(4).open(true);
assertEquals(4, stream.id());
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
assertEquals(2, server.activeStreams().size());
assertEquals(4, server.local().lastStreamCreated());
stream = server.remote().createStream(3, true);
stream = server.remote().createStream(3).open(true);
assertEquals(3, stream.id());
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
assertEquals(3, server.activeStreams().size());
assertEquals(3, server.remote().lastStreamCreated());
stream = server.remote().createStream(5, false);
stream = server.remote().createStream(5).open(false);
assertEquals(5, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(4, server.activeStreams().size());
@ -99,25 +101,25 @@ public class DefaultHttp2ConnectionTest {
@Test
public void clientCreateStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = client.remote().createStream(2, false);
Http2Stream stream = client.remote().createStream(2).open(false);
assertEquals(2, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(1, client.activeStreams().size());
assertEquals(2, client.remote().lastStreamCreated());
stream = client.remote().createStream(4, true);
stream = client.remote().createStream(4).open(true);
assertEquals(4, stream.id());
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
assertEquals(2, client.activeStreams().size());
assertEquals(4, client.remote().lastStreamCreated());
stream = client.local().createStream(3, true);
stream = client.local().createStream(3).open(true);
assertEquals(3, stream.id());
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
assertEquals(3, client.activeStreams().size());
assertEquals(3, client.local().lastStreamCreated());
stream = client.local().createStream(5, false);
stream = client.local().createStream(5).open(false);
assertEquals(5, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(4, client.activeStreams().size());
@ -126,7 +128,7 @@ public class DefaultHttp2ConnectionTest {
@Test
public void serverReservePushStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
Http2Stream stream = server.remote().createStream(3).open(true);
Http2Stream pushStream = server.local().reservePushStream(2, stream);
assertEquals(2, pushStream.id());
assertEquals(State.RESERVED_LOCAL, pushStream.state());
@ -136,7 +138,7 @@ public class DefaultHttp2ConnectionTest {
@Test
public void clientReservePushStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
Http2Stream stream = server.remote().createStream(3).open(true);
Http2Stream pushStream = server.local().reservePushStream(4, stream);
assertEquals(4, pushStream.id());
assertEquals(State.RESERVED_LOCAL, pushStream.state());
@ -146,28 +148,28 @@ public class DefaultHttp2ConnectionTest {
@Test(expected = Http2Exception.class)
public void newStreamBehindExpectedShouldThrow() throws Http2Exception {
server.local().createStream(0, true);
server.local().createStream(0).open(true);
}
@Test(expected = Http2Exception.class)
public void newStreamNotForServerShouldThrow() throws Http2Exception {
server.local().createStream(11, true);
server.local().createStream(11).open(true);
}
@Test(expected = Http2Exception.class)
public void newStreamNotForClientShouldThrow() throws Http2Exception {
client.local().createStream(10, true);
client.local().createStream(10).open(true);
}
@Test(expected = Http2Exception.class)
public void maxAllowedStreamsExceededShouldThrow() throws Http2Exception {
server.local().maxStreams(0);
server.local().createStream(2, true);
server.local().createStream(2).open(true);
}
@Test(expected = Http2Exception.class)
public void reserveWithPushDisallowedShouldThrow() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
Http2Stream stream = server.remote().createStream(3).open(true);
server.remote().allowPushTo(false);
server.local().reservePushStream(2, stream);
}
@ -175,12 +177,12 @@ public class DefaultHttp2ConnectionTest {
@Test(expected = Http2Exception.class)
public void goAwayReceivedShouldDisallowCreation() throws Http2Exception {
server.goAwayReceived(0);
server.remote().createStream(3, true);
server.remote().createStream(3).open(true);
}
@Test
public void closeShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
Http2Stream stream = server.remote().createStream(3).open(true);
stream.close();
assertEquals(State.CLOSED, stream.state());
assertTrue(server.activeStreams().isEmpty());
@ -188,7 +190,7 @@ public class DefaultHttp2ConnectionTest {
@Test
public void closeLocalWhenOpenShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, false);
Http2Stream stream = server.remote().createStream(3).open(false);
stream.closeLocalSide();
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
assertEquals(1, server.activeStreams().size());
@ -196,7 +198,7 @@ public class DefaultHttp2ConnectionTest {
@Test
public void closeRemoteWhenOpenShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, false);
Http2Stream stream = server.remote().createStream(3).open(false);
stream.closeRemoteSide();
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
assertEquals(1, server.activeStreams().size());
@ -204,7 +206,7 @@ public class DefaultHttp2ConnectionTest {
@Test
public void closeOnlyOpenSideShouldClose() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
Http2Stream stream = server.remote().createStream(3).open(true);
stream.closeLocalSide();
assertEquals(State.CLOSED, stream.state());
assertTrue(server.activeStreams().isEmpty());
@ -212,17 +214,31 @@ public class DefaultHttp2ConnectionTest {
@Test(expected = Http2Exception.class)
public void localStreamInvalidStreamIdShouldThrow() throws Http2Exception {
client.createLocalStream(Integer.MAX_VALUE + 2, false);
client.createLocalStream(Integer.MAX_VALUE + 2).open(false);
}
@Test(expected = Http2Exception.class)
public void remoteStreamInvalidStreamIdShouldThrow() throws Http2Exception {
client.createRemoteStream(Integer.MAX_VALUE + 1, false);
client.createRemoteStream(Integer.MAX_VALUE + 1).open(false);
}
@Test
public void localStreamCanDependUponIdleStream() throws Http2Exception {
Http2Stream streamA = client.local().createStream(1).open(false);
streamA.setPriority(3, MIN_WEIGHT, true);
verifyDependUponIdleStream(streamA, client.stream(3), client.local());
}
@Test
public void remoteStreamCanDependUponIdleStream() throws Http2Exception {
Http2Stream streamA = client.remote().createStream(2).open(false);
streamA.setPriority(4, MIN_WEIGHT, true);
verifyDependUponIdleStream(streamA, client.stream(4), client.remote());
}
@Test
public void prioritizeShouldUseDefaults() throws Exception {
Http2Stream stream = client.local().createStream(1, false);
Http2Stream stream = client.local().createStream(1).open(false);
assertEquals(1, client.connectionStream().numChildren());
assertEquals(stream, client.connectionStream().child(1));
assertEquals(DEFAULT_PRIORITY_WEIGHT, stream.weight());
@ -232,7 +248,7 @@ public class DefaultHttp2ConnectionTest {
@Test
public void reprioritizeWithNoChangeShouldDoNothing() throws Exception {
Http2Stream stream = client.local().createStream(1, false);
Http2Stream stream = client.local().createStream(1).open(false);
stream.setPriority(0, DEFAULT_PRIORITY_WEIGHT, false);
assertEquals(1, client.connectionStream().numChildren());
assertEquals(stream, client.connectionStream().child(1));
@ -243,10 +259,10 @@ public class DefaultHttp2ConnectionTest {
@Test
public void insertExclusiveShouldAddNewLevel() throws Exception {
Http2Stream streamA = client.local().createStream(1, false);
Http2Stream streamB = client.local().createStream(3, false);
Http2Stream streamC = client.local().createStream(5, false);
Http2Stream streamD = client.local().createStream(7, false);
Http2Stream streamA = client.local().createStream(1).open(false);
Http2Stream streamB = client.local().createStream(3).open(false);
Http2Stream streamC = client.local().createStream(5).open(false);
Http2Stream streamD = client.local().createStream(7).open(false);
streamB.setPriority(streamA.id(), DEFAULT_PRIORITY_WEIGHT, false);
streamC.setPriority(streamA.id(), DEFAULT_PRIORITY_WEIGHT, false);
@ -288,10 +304,10 @@ public class DefaultHttp2ConnectionTest {
@Test
public void weightChangeWithNoTreeChangeShouldNotifyListeners() throws Http2Exception {
Http2Stream streamA = client.local().createStream(1, false);
Http2Stream streamB = client.local().createStream(3, false);
Http2Stream streamC = client.local().createStream(5, false);
Http2Stream streamD = client.local().createStream(7, false);
Http2Stream streamA = client.local().createStream(1).open(false);
Http2Stream streamB = client.local().createStream(3).open(false);
Http2Stream streamC = client.local().createStream(5).open(false);
Http2Stream streamD = client.local().createStream(7).open(false);
streamB.setPriority(streamA.id(), DEFAULT_PRIORITY_WEIGHT, false);
streamC.setPriority(streamA.id(), DEFAULT_PRIORITY_WEIGHT, false);
@ -313,10 +329,10 @@ public class DefaultHttp2ConnectionTest {
@Test
public void removeShouldRestructureTree() throws Exception {
Http2Stream streamA = client.local().createStream(1, false);
Http2Stream streamB = client.local().createStream(3, false);
Http2Stream streamC = client.local().createStream(5, false);
Http2Stream streamD = client.local().createStream(7, false);
Http2Stream streamA = client.local().createStream(1).open(false);
Http2Stream streamB = client.local().createStream(3).open(false);
Http2Stream streamC = client.local().createStream(5).open(false);
Http2Stream streamD = client.local().createStream(7).open(false);
streamB.setPriority(streamA.id(), DEFAULT_PRIORITY_WEIGHT, false);
streamC.setPriority(streamB.id(), DEFAULT_PRIORITY_WEIGHT, false);
@ -352,19 +368,19 @@ public class DefaultHttp2ConnectionTest {
@Test
public void circularDependencyShouldRestructureTree() throws Exception {
// Using example from http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.3.3
// Using example from http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.3.3
// Initialize all the nodes
Http2Stream streamA = client.local().createStream(1, false);
Http2Stream streamA = client.local().createStream(1).open(false);
verifyParentChanged(streamA, null);
Http2Stream streamB = client.local().createStream(3, false);
Http2Stream streamB = client.local().createStream(3).open(false);
verifyParentChanged(streamB, null);
Http2Stream streamC = client.local().createStream(5, false);
Http2Stream streamC = client.local().createStream(5).open(false);
verifyParentChanged(streamC, null);
Http2Stream streamD = client.local().createStream(7, false);
Http2Stream streamD = client.local().createStream(7).open(false);
verifyParentChanged(streamD, null);
Http2Stream streamE = client.local().createStream(9, false);
Http2Stream streamE = client.local().createStream(9).open(false);
verifyParentChanged(streamE, null);
Http2Stream streamF = client.local().createStream(11, false);
Http2Stream streamF = client.local().createStream(11).open(false);
verifyParentChanged(streamF, null);
// Build the tree
@ -442,19 +458,19 @@ public class DefaultHttp2ConnectionTest {
@Test
public void circularDependencyWithExclusiveShouldRestructureTree() throws Exception {
// Using example from http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.3.3
// Using example from http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.3.3
// Initialize all the nodes
Http2Stream streamA = client.local().createStream(1, false);
Http2Stream streamA = client.local().createStream(1).open(false);
verifyParentChanged(streamA, null);
Http2Stream streamB = client.local().createStream(3, false);
Http2Stream streamB = client.local().createStream(3).open(false);
verifyParentChanged(streamB, null);
Http2Stream streamC = client.local().createStream(5, false);
Http2Stream streamC = client.local().createStream(5).open(false);
verifyParentChanged(streamC, null);
Http2Stream streamD = client.local().createStream(7, false);
Http2Stream streamD = client.local().createStream(7).open(false);
verifyParentChanged(streamD, null);
Http2Stream streamE = client.local().createStream(9, false);
Http2Stream streamE = client.local().createStream(9).open(false);
verifyParentChanged(streamE, null);
Http2Stream streamF = client.local().createStream(11, false);
Http2Stream streamF = client.local().createStream(11).open(false);
verifyParentChanged(streamF, null);
// Build the tree
@ -564,6 +580,17 @@ public class DefaultHttp2ConnectionTest {
}
}
private static void verifyDependUponIdleStream(Http2Stream streamA, Http2Stream streamB, Endpoint<?> endpoint) {
assertNotNull(streamB);
assertEquals(streamB.id(), endpoint.lastStreamCreated());
assertEquals(State.IDLE, streamB.state());
assertEquals(MIN_WEIGHT, streamA.weight());
assertEquals(DEFAULT_PRIORITY_WEIGHT, streamB.weight());
assertEquals(streamB, streamA.parent());
assertEquals(1, streamB.numChildren());
assertEquals(streamA, streamB.children().iterator().next());
}
@SuppressWarnings("unchecked")
private static <T> T streamEq(T stream) {
return (T) (stream == null ? isNull(Http2Stream.class) : eq(stream));

View File

@ -15,17 +15,20 @@
package io.netty.handler.codec.http2;
import com.twitter.hpack.Encoder;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2TestUtil.as;
import static io.netty.handler.codec.http2.Http2TestUtil.randomBytes;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.junit.Assert.assertEquals;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import static io.netty.handler.codec.http2.Http2TestUtil.*;
import static io.netty.util.CharsetUtil.*;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import com.twitter.hpack.Encoder;
/**
* Tests for {@link DefaultHttp2HeadersDecoder}.
@ -57,7 +60,7 @@ public class DefaultHttp2HeadersDecoderTest {
}
private static ByteBuf encode(byte[]... entries) throws Exception {
Encoder encoder = new Encoder();
Encoder encoder = new Encoder(MAX_HEADER_TABLE_SIZE);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (int ix = 0; ix < entries.length;) {
byte[] key = entries[ix++];

View File

@ -68,7 +68,7 @@ public class DefaultHttp2LocalFlowControllerTest {
connection = new DefaultHttp2Connection(false);
controller = new DefaultHttp2LocalFlowController(connection, frameWriter, updateRatio);
connection.local().createStream(STREAM_ID, false);
connection.local().createStream(STREAM_ID).open(false);
}
@Test
@ -156,7 +156,7 @@ public class DefaultHttp2LocalFlowControllerTest {
@Test
public void connectionWindowShouldAdjustWithMultipleStreams() throws Http2Exception {
int newStreamId = 3;
connection.local().createStream(newStreamId, false);
connection.local().createStream(newStreamId).open(false);
try {
assertEquals(DEFAULT_WINDOW_SIZE, window(STREAM_ID));
@ -218,7 +218,7 @@ public class DefaultHttp2LocalFlowControllerTest {
throws Http2Exception {
int delta = newDefaultWindowSize - DEFAULT_WINDOW_SIZE;
controller.incrementWindowSize(ctx, stream(0), delta);
Http2Stream stream = connection.local().createStream(newStreamId, false);
Http2Stream stream = connection.local().createStream(newStreamId).open(false);
if (setStreamRatio) {
controller.windowUpdateRatio(ctx, stream, ratio);
}

View File

@ -88,10 +88,10 @@ public class DefaultHttp2RemoteFlowControllerTest {
connection = new DefaultHttp2Connection(false);
controller = new DefaultHttp2RemoteFlowController(connection, frameWriter);
connection.local().createStream(STREAM_A, false);
connection.local().createStream(STREAM_B, false);
Http2Stream streamC = connection.local().createStream(STREAM_C, false);
Http2Stream streamD = connection.local().createStream(STREAM_D, false);
connection.local().createStream(STREAM_A).open(false);
connection.local().createStream(STREAM_B).open(false);
Http2Stream streamC = connection.local().createStream(STREAM_C).open(false);
Http2Stream streamD = connection.local().createStream(STREAM_D).open(false);
streamC.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false);
streamD.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false);
@ -1158,7 +1158,7 @@ public class DefaultHttp2RemoteFlowControllerTest {
Http2Stream streamC = connection.stream(STREAM_C);
Http2Stream streamD = connection.stream(STREAM_D);
Http2Stream streamE = connection.local().createStream(STREAM_E, false);
Http2Stream streamE = connection.local().createStream(STREAM_E).open(false);
streamE.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, true);
// Send a bunch of data on each stream.

View File

@ -60,10 +60,10 @@ public class Http2ConnectionHandlerTest {
private Http2Connection connection;
@Mock
private Http2Connection.Endpoint remote;
private Http2Connection.Endpoint<Http2RemoteFlowController> remote;
@Mock
private Http2Connection.Endpoint local;
private Http2Connection.Endpoint<Http2LocalFlowController> local;
@Mock
private ChannelHandlerContext ctx;
@ -110,20 +110,21 @@ public class Http2ConnectionHandlerTest {
when(connection.remote()).thenReturn(remote);
when(connection.local()).thenReturn(local);
when(connection.activeStreams()).thenReturn(Collections.singletonList(stream));
when(stream.open(anyBoolean())).thenReturn(stream);
doAnswer(new Answer<Http2Stream>() {
@Override
public Http2Stream answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return local.createStream((Integer) args[0], (Boolean) args[1]);
return local.createStream((Integer) args[0]);
}
}).when(connection).createLocalStream(anyInt(), anyBoolean());
}).when(connection).createLocalStream(anyInt());
doAnswer(new Answer<Http2Stream>() {
@Override
public Http2Stream answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return remote.createStream((Integer) args[0], (Boolean) args[1]);
return remote.createStream((Integer) args[0]);
}
}).when(connection).createRemoteStream(anyInt(), anyBoolean());
}).when(connection).createRemoteStream(anyInt());
when(encoder.writeSettings(eq(ctx), any(Http2Settings.class), eq(promise))).thenReturn(future);
when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel);

View File

@ -116,9 +116,9 @@ final class Http2TestUtil {
Http2Stream stream = connection.stream(streamId);
if (stream == null) {
if (connection.isServer() && streamId % 2 == 0 || !connection.isServer() && streamId % 2 != 0) {
stream = connection.local().createStream(streamId, halfClosed);
stream = connection.local().createStream(streamId).open(halfClosed);
} else {
stream = connection.remote().createStream(streamId, halfClosed);
stream = connection.remote().createStream(streamId).open(halfClosed);
}
}
return stream;

View File

@ -505,7 +505,7 @@
<dependency>
<groupId>com.twitter</groupId>
<artifactId>hpack</artifactId>
<version>0.9.1</version>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.npn</groupId>