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:
parent
2cf4ee9322
commit
1293aba28e
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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() { }
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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++];
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user