HTTP/2 Draft 15

Motivation:
A new draft of the HTTP/2 spec has been released.

Modifications:
Make updates as defined in https://tools.ietf.org/html/draft-ietf-httpbis-http2-15

Result:
HTTP/2 codec is draft 15 compliant.
This commit is contained in:
Scott Mitchell 2014-11-20 23:40:35 -05:00
parent 95540bd49e
commit a8e5fb12fa
28 changed files with 404 additions and 308 deletions

View File

@ -118,6 +118,8 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
// Check the absolutely prohibited characters.
switch (character) {
case 0x0: // NULL
throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
case 0x0b: // Vertical tab
throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
case '\f':

View File

@ -20,7 +20,8 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGH
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.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
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;
@ -116,7 +117,7 @@ public class DefaultHttp2Connection implements Http2Connection {
public Http2Stream requireStream(int streamId) throws Http2Exception {
Http2Stream stream = stream(streamId);
if (stream == null) {
throw protocolError("Stream does not exist %d", streamId);
throw connectionError(PROTOCOL_ERROR, "Stream does not exist %d", streamId);
}
return stream;
}
@ -430,7 +431,7 @@ public class DefaultHttp2Connection implements Http2Connection {
state = HALF_CLOSED_LOCAL;
break;
default:
throw protocolError("Attempting to open non-reserved stream for push");
throw connectionError(PROTOCOL_ERROR, "Attempting to open non-reserved stream for push");
}
activate(this);
return this;
@ -820,14 +821,15 @@ public class DefaultHttp2Connection implements Http2Connection {
@Override
public DefaultStream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception {
if (parent == null) {
throw protocolError("Parent stream missing");
throw connectionError(PROTOCOL_ERROR, "Parent stream missing");
}
if (isLocal() ? !parent.localSideOpen() : !parent.remoteSideOpen()) {
throw protocolError("Stream %d is not open for sending push promise", parent.id());
throw connectionError(PROTOCOL_ERROR, "Stream %d is not open for sending push promise", parent.id());
}
if (!opposite().allowPushTo()) {
throw protocolError("Server push not allowed to opposite endpoint.");
throw connectionError(PROTOCOL_ERROR, "Server push not allowed to opposite endpoint.");
}
checkNewStreamAllowed(streamId);
// Create and initialize the stream.
DefaultStream stream = new DefaultStream(streamId);
@ -915,11 +917,11 @@ public class DefaultHttp2Connection implements Http2Connection {
private void checkNewStreamAllowed(int streamId) throws Http2Exception {
if (isGoAway()) {
throw protocolError("Cannot create a stream since the connection is going away");
throw connectionError(PROTOCOL_ERROR, "Cannot create a stream since the connection is going away");
}
verifyStreamId(streamId);
if (!acceptingNewStreams()) {
throw protocolError("Maximum streams exceeded for this endpoint.");
throw connectionError(PROTOCOL_ERROR, "Maximum streams exceeded for this endpoint.");
}
}
@ -928,11 +930,12 @@ public class DefaultHttp2Connection implements Http2Connection {
throw new Http2NoMoreStreamIdsException();
}
if (streamId < nextStreamId) {
throw protocolError("Request stream %d is behind the next expected stream %d", streamId, nextStreamId);
throw connectionError(PROTOCOL_ERROR, "Request stream %d is behind the next expected stream %d",
streamId, nextStreamId);
}
if (!createdStreamId(streamId)) {
throw protocolError("Request stream %d is not correct for %s connection", streamId, server ? "server"
: "client");
throw connectionError(PROTOCOL_ERROR, "Request stream %d is not correct for %s connection",
streamId, server ? "server" : "client");
}
}

View File

@ -16,9 +16,10 @@ 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.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
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.Http2StreamException.streamClosedError;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
@ -160,7 +161,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
Http2FrameSizePolicy inboundFrameSizePolicy = config.frameSizePolicy();
if (pushEnabled != null) {
if (connection.isServer()) {
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
}
connection.local().allowPushTo(pushEnabled);
}
@ -234,16 +235,14 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
shouldApplyFlowControl = true;
}
if (!shouldIgnore) {
// Stream error.
error = streamClosedError(stream.id(), "Stream %d in unexpected state: %s",
error = streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
stream.id(), stream.state());
}
break;
default:
if (!shouldIgnore) {
// Connection error.
error = protocolError("Stream %d in unexpected state: %s", stream.id(),
stream.state());
error = streamError(stream.id(), PROTOCOL_ERROR,
"Stream %d in unexpected state: %s", stream.id(), stream.state());
}
break;
}
@ -304,7 +303,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
*/
private void verifyPrefaceReceived() throws Http2Exception {
if (!prefaceReceived) {
throw protocolError("Received non-SETTINGS as first frame.");
throw connectionError(PROTOCOL_ERROR, "Received non-SETTINGS as first frame.");
}
}
@ -345,11 +344,11 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
case HALF_CLOSED_REMOTE:
case CLOSED:
// Stream error.
throw streamClosedError(stream.id(), "Stream %d in unexpected state: %s",
throw streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
stream.id(), stream.state());
default:
// Connection error.
throw protocolError("Stream %d in unexpected state: %s", stream.id(),
throw connectionError(PROTOCOL_ERROR, "Stream %d in unexpected state: %s", stream.id(),
stream.state());
}
}
@ -373,8 +372,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
Http2Stream stream = connection.requireStream(streamId);
verifyGoAwayNotReceived();
verifyRstStreamNotReceived(stream);
if (stream.state() == CLOSED || shouldIgnoreFrame(stream)) {
if (shouldIgnoreFrame(stream)) {
// Ignore frames for any stream created after we sent a go-away.
return;
}
@ -426,7 +424,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
final Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy();
if (pushEnabled != null) {
if (connection.isServer()) {
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
}
connection.local().allowPushTo(pushEnabled);
}
@ -578,7 +576,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
private void verifyGoAwayNotReceived() throws Http2Exception {
if (connection.goAwayReceived()) {
// Connection error.
throw protocolError("Received frames after receiving GO_AWAY");
throw connectionError(PROTOCOL_ERROR, "Received frames after receiving GO_AWAY");
}
}
@ -589,7 +587,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
private void verifyRstStreamNotReceived(Http2Stream stream) throws Http2Exception {
if (stream != null && stream.isResetReceived()) {
// Stream error.
throw streamClosedError(stream.id(),
throw streamError(stream.id(), STREAM_CLOSED,
"Frame received after receiving RST_STREAM for stream: " + stream.id());
}
}

View File

@ -15,7 +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.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
@ -114,7 +115,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
Http2FrameSizePolicy outboundFrameSizePolicy = config.frameSizePolicy();
if (pushEnabled != null) {
if (!connection.isServer()) {
throw protocolError("Client received SETTINGS frame with ENABLE_PUSH specified");
throw connectionError(PROTOCOL_ERROR, "Client received SETTINGS frame with ENABLE_PUSH specified");
}
connection.remote().allowPushTo(pushEnabled);
}
@ -221,7 +222,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
ChannelFuture lastDataWrite = lastWriteForStream(streamId);
try {
if (connection.isGoAway()) {
throw protocolError("Sending headers after connection going away.");
throw connectionError(PROTOCOL_ERROR, "Sending headers after connection going away.");
}
if (stream == null) {
@ -317,7 +318,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
boolean exclusive, ChannelPromise promise) {
try {
if (connection.isGoAway()) {
throw protocolError("Sending priority after connection going away.");
throw connectionError(PROTOCOL_ERROR, "Sending priority after connection going away.");
}
// Update the priority on this stream.
@ -379,12 +380,12 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
outstandingLocalSettingsQueue.add(settings);
try {
if (connection.isGoAway()) {
throw protocolError("Sending settings after connection going away.");
throw connectionError(PROTOCOL_ERROR, "Sending settings after connection going away.");
}
Boolean pushEnabled = settings.pushEnabled();
if (pushEnabled != null && connection.isServer()) {
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
}
} catch (Throwable e) {
return promise.setFailure(e);
@ -405,7 +406,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
ChannelPromise promise) {
if (connection.isGoAway()) {
data.release();
return promise.setFailure(protocolError("Sending ping after connection going away."));
return promise.setFailure(connectionError(PROTOCOL_ERROR, "Sending ping after connection going away."));
}
ChannelFuture future = frameWriter.writePing(ctx, ack, data, promise);
@ -418,7 +419,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
Http2Headers headers, int padding, ChannelPromise promise) {
try {
if (connection.isGoAway()) {
throw protocolError("Sending push promise after connection going away.");
throw connectionError(PROTOCOL_ERROR, "Sending push promise after connection going away.");
}
// Reserve the promised stream.

View File

@ -14,16 +14,18 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_FRAME_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
import static io.netty.handler.codec.http2.Http2CodecUtil.readUnsignedInt;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
@ -86,7 +88,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
@Override
public void maxFrameSize(int max) throws Http2Exception {
if (!isMaxFrameSizeValid(max)) {
Http2Exception.format(Http2Error.FRAME_SIZE_ERROR,
throw streamError(streamId, FRAME_SIZE_ERROR,
"Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
}
maxFrameSize = max;
@ -157,7 +159,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
// Read the header and prepare the unmarshaller to read the frame.
payloadLength = in.readUnsignedMedium();
if (payloadLength > maxFrameSize) {
throw protocolError("Frame length: %d exceeds maximum: %d", payloadLength, maxFrameSize);
throw connectionError(PROTOCOL_ERROR, "Frame length: %d exceeds maximum: %d", payloadLength, maxFrameSize);
}
frameType = in.readByte();
flags = new Http2Flags(in.readUnsignedByte());
@ -259,7 +261,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyPayloadLength(payloadLength);
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
throw protocolError("Frame length %d too small.", payloadLength);
throw streamError(streamId, FRAME_SIZE_ERROR,
"Frame length %d too small.", payloadLength);
}
}
@ -269,7 +272,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
int requiredLength = flags.getPaddingPresenceFieldLength() + flags.getNumPriorityBytes();
if (payloadLength < requiredLength) {
throw protocolError("Frame length too small." + payloadLength);
throw streamError(streamId, FRAME_SIZE_ERROR,
"Frame length too small." + payloadLength);
}
}
@ -277,7 +281,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyNotProcessingHeaders();
if (payloadLength != PRIORITY_ENTRY_LENGTH) {
throw protocolError("Invalid frame length %d.", payloadLength);
throw streamError(streamId, FRAME_SIZE_ERROR,
"Invalid frame length %d.", payloadLength);
}
}
@ -285,7 +290,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyNotProcessingHeaders();
if (payloadLength != INT_FIELD_LENGTH) {
throw protocolError("Invalid frame length %d.", payloadLength);
throw streamError(streamId, FRAME_SIZE_ERROR,
"Invalid frame length %d.", payloadLength);
}
}
@ -293,13 +299,13 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyNotProcessingHeaders();
verifyPayloadLength(payloadLength);
if (streamId != 0) {
throw protocolError("A stream ID must be zero.");
throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
}
if (flags.ack() && payloadLength > 0) {
throw protocolError("Ack settings frame must have an empty payload.");
throw connectionError(PROTOCOL_ERROR, "Ack settings frame must have an empty payload.");
}
if (payloadLength % SETTING_ENTRY_LENGTH > 0) {
throw protocolError("Frame length %d invalid.", payloadLength);
throw connectionError(FRAME_SIZE_ERROR, "Frame length %d invalid.", payloadLength);
}
}
@ -311,17 +317,19 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
// rest of the payload (header block fragment + payload).
int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
if (payloadLength < minLength) {
throw protocolError("Frame length %d too small.", payloadLength);
throw streamError(streamId, FRAME_SIZE_ERROR,
"Frame length %d too small.", payloadLength);
}
}
private void verifyPingFrame() throws Http2Exception {
verifyNotProcessingHeaders();
if (streamId != 0) {
throw protocolError("A stream ID must be zero.");
throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
}
if (payloadLength != 8) {
throw protocolError("Frame length %d incorrect size for ping.", payloadLength);
throw connectionError(FRAME_SIZE_ERROR,
"Frame length %d incorrect size for ping.", payloadLength);
}
}
@ -330,10 +338,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyPayloadLength(payloadLength);
if (streamId != 0) {
throw protocolError("A stream ID must be zero.");
throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
}
if (payloadLength < 8) {
throw protocolError("Frame length %d too small.", payloadLength);
throw connectionError(FRAME_SIZE_ERROR, "Frame length %d too small.", payloadLength);
}
}
@ -342,7 +350,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyStreamOrConnectionId(streamId, "Stream ID");
if (payloadLength != INT_FIELD_LENGTH) {
throw protocolError("Invalid frame length %d.", payloadLength);
throw streamError(streamId, FRAME_SIZE_ERROR,
"Invalid frame length %d.", payloadLength);
}
}
@ -350,17 +359,18 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyPayloadLength(payloadLength);
if (headersContinuation == null) {
throw protocolError("Received %s frame but not currently processing headers.",
throw connectionError(PROTOCOL_ERROR, "Received %s frame but not currently processing headers.",
frameType);
}
if (streamId != headersContinuation.getStreamId()) {
throw protocolError("Continuation stream ID does not match pending headers. "
throw connectionError(PROTOCOL_ERROR, "Continuation stream ID does not match pending headers. "
+ "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
}
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
throw protocolError("Frame length %d too small for padding.", payloadLength);
throw streamError(streamId, FRAME_SIZE_ERROR,
"Frame length %d too small for padding.", payloadLength);
}
}
@ -372,7 +382,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
// padding.
int dataLength = payload.readableBytes() - padding;
if (dataLength < 0) {
throw protocolError("Frame payload too small for padding.");
throw streamError(streamId, FRAME_SIZE_ERROR,
"Frame payload too small for padding.");
}
ByteBuf data = payload.readSlice(dataLength);
@ -475,9 +486,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
settings.put(id, value);
} catch (IllegalArgumentException e) {
if (id == SETTINGS_MAX_FRAME_SIZE) {
throw new Http2Exception(Http2Error.FRAME_SIZE_ERROR, e.getMessage(), e);
throw new Http2Exception(FRAME_SIZE_ERROR, e.getMessage(), e);
} else {
throw new Http2Exception(Http2Error.PROTOCOL_ERROR, e.getMessage(), e);
throw new Http2Exception(PROTOCOL_ERROR, e.getMessage(), e);
}
}
}
@ -538,14 +549,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
Http2FrameListener listener) throws Http2Exception {
int windowSizeIncrement = readUnsignedInt(payload);
if (windowSizeIncrement == 0) {
if (streamId == CONNECTION_STREAM_ID) {
// It's a connection error.
throw protocolError("Received WINDOW_UPDATE with delta 0 for connection stream");
} else {
// It's a stream error.
throw new Http2StreamException(streamId, Http2Error.PROTOCOL_ERROR,
"Received WINDOW_UPDATE with delta 0 for stream: " + streamId);
}
throw streamError(streamId, PROTOCOL_ERROR,
"Received WINDOW_UPDATE with delta 0 for stream: %d", streamId);
}
listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
}
@ -677,20 +682,20 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
private void verifyNotProcessingHeaders() throws Http2Exception {
if (headersContinuation != null) {
throw protocolError("Received frame of type %s while processing headers.", frameType);
throw connectionError(PROTOCOL_ERROR, "Received frame of type %s while processing headers.", frameType);
}
}
private void verifyPayloadLength(int payloadLength) throws Http2Exception {
if (payloadLength > maxFrameSize) {
throw protocolError("Total payload length %d exceeds max frame length.", payloadLength);
throw connectionError(PROTOCOL_ERROR, "Total payload length %d exceeds max frame length.", payloadLength);
}
}
private static void verifyStreamOrConnectionId(int streamId, String argumentName)
throws Http2Exception {
if (streamId < 0) {
throw protocolError("%s must be >= 0", argumentName);
throw connectionError(PROTOCOL_ERROR, "%s must be >= 0", argumentName);
}
}
}

View File

@ -15,6 +15,7 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
@ -85,7 +86,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public void maxFrameSize(int max) throws Http2Exception {
if (!isMaxFrameSizeValid(max)) {
Http2Exception.format(FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
connectionError(FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
}
maxFrameSize = max;
}

View File

@ -14,6 +14,9 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
/**
* Provides common functionality for {@link Http2HeaderTable}
*/
@ -22,7 +25,7 @@ class DefaultHttp2HeaderTableListSize {
public void maxHeaderListSize(int max) throws Http2Exception {
if (max < 0) {
throw Http2Exception.protocolError("Header List Size must be non-negative but was %d", max);
throw connectionError(PROTOCOL_ERROR, "Header List Size must be non-negative but was %d", max);
}
maxHeaderListSize = max;
}

View File

@ -17,8 +17,10 @@ package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_HEADER_SIZE;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.handler.codec.AsciiString;
@ -71,7 +73,7 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
}
if (headers.size() > headerTable.maxHeaderListSize()) {
throw protocolError("Number of headers (%d) exceeds maxHeaderListSize (%d)",
throw connectionError(PROTOCOL_ERROR, "Number of headers (%d) exceeds maxHeaderListSize (%d)",
headers.size(), headerTable.maxHeaderListSize());
}
@ -82,12 +84,12 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
// Default handler for any other types of errors that may have occurred. For example,
// the the Header builder throws IllegalArgumentException if the key or value was invalid
// for any reason (e.g. the key was an invalid pseudo-header).
throw new Http2Exception(Http2Error.PROTOCOL_ERROR, e.getMessage(), e);
throw new Http2Exception(PROTOCOL_ERROR, e.getMessage(), e);
} finally {
try {
in.close();
} catch (IOException e) {
throw new Http2Exception(Http2Error.INTERNAL_ERROR, e.getMessage(), e);
throw new Http2Exception(INTERNAL_ERROR, e.getMessage(), e);
}
}
}
@ -99,12 +101,12 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
@Override
public void maxHeaderTableSize(int max) throws Http2Exception {
if (max < 0) {
throw protocolError("Header Table Size must be non-negative but was %d", max);
throw connectionError(PROTOCOL_ERROR, "Header Table Size must be non-negative but was %d", max);
}
try {
decoder.setMaxHeaderTableSize(max);
} catch (Throwable t) {
throw Http2Exception.format(Http2Error.PROTOCOL_ERROR, t.getMessage(), t);
throw connectionError(PROTOCOL_ERROR, t.getMessage(), t);
}
}

View File

@ -16,7 +16,10 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.handler.codec.AsciiString;
@ -53,7 +56,7 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
final OutputStream stream = new ByteBufOutputStream(buffer);
try {
if (headers.size() > headerTable.maxHeaderListSize()) {
throw protocolError("Number of headers (%d) exceeds maxHeaderListSize (%d)",
throw connectionError(PROTOCOL_ERROR, "Number of headers (%d) exceeds maxHeaderListSize (%d)",
headers.size(), headerTable.maxHeaderListSize());
}
@ -85,13 +88,13 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
}
});
} catch (Exception e) {
throw Http2Exception.format(Http2Error.COMPRESSION_ERROR, "Failed encoding headers block: %s",
throw connectionError(COMPRESSION_ERROR, "Failed encoding headers block: %s",
e.getMessage());
} finally {
try {
stream.close();
} catch (IOException e) {
throw new Http2Exception(Http2Error.INTERNAL_ERROR, e.getMessage(), e);
throw new Http2Exception(INTERNAL_ERROR, e.getMessage(), e);
}
}
}
@ -118,15 +121,15 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
@Override
public void maxHeaderTableSize(int max) throws Http2Exception {
if (max < 0) {
throw protocolError("Header Table Size must be non-negative but was %d", max);
throw connectionError(PROTOCOL_ERROR, "Header Table Size must be non-negative but was %d", max);
}
try {
// No headers should be emitted. If they are, we throw.
encoder.setMaxHeaderTableSize(tableSizeChangeOutput, max);
} catch (IOException e) {
throw new Http2Exception(Http2Error.COMPRESSION_ERROR, e.getMessage(), e);
throw new Http2Exception(COMPRESSION_ERROR, e.getMessage(), e);
} catch (Throwable t) {
throw new Http2Exception(Http2Error.PROTOCOL_ERROR, t.getMessage(), t);
throw new Http2Exception(PROTOCOL_ERROR, t.getMessage(), t);
}
}

View File

@ -19,8 +19,9 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.flowControlError;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
@ -139,7 +140,7 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro
private FlowState stateOrFail(int streamId) throws Http2Exception {
FlowState state = state(streamId);
if (state == null) {
throw protocolError("Flow control window missing for stream: %d", streamId);
throw connectionError(PROTOCOL_ERROR, "Flow control window missing for stream: %d", streamId);
}
return state;
}
@ -244,12 +245,8 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro
*/
void returnProcessedBytes(int delta) throws Http2Exception {
if (processedWindow - delta < window) {
if (stream.id() == CONNECTION_STREAM_ID) {
throw new Http2Exception(INTERNAL_ERROR,
"Attempting to return too many bytes for connection");
}
throw new Http2StreamException(stream.id(), INTERNAL_ERROR,
"Attempting to return too many bytes for stream " + stream.id());
throw streamError(stream.id(), INTERNAL_ERROR,
"Attempting to return too many bytes for stream %d", stream.id());
}
processedWindow -= delta;
}
@ -275,9 +272,9 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro
// and is cleared once we send a WINDOW_UPDATE frame.
if (delta < 0 && window < lowerBound) {
if (stream.id() == CONNECTION_STREAM_ID) {
throw flowControlError("Connection flow control window exceeded");
throw connectionError(FLOW_CONTROL_ERROR, "Connection flow control window exceeded");
} else {
throw Http2StreamException.format(stream.id(), FLOW_CONTROL_ERROR,
throw streamError(stream.id(), FLOW_CONTROL_ERROR,
"Flow control window exceeded for stream: %d", stream.id());
}
}
@ -295,9 +292,8 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro
* @throws Http2Exception thrown if integer overflow occurs on the window.
*/
void updatedInitialWindowSize(int delta) throws Http2Exception {
if (delta > 0 && window > Integer.MAX_VALUE - delta) {
// Integer overflow.
throw flowControlError("Flow control window overflowed for stream: %d", stream.id());
if (delta > 0 && window > Integer.MAX_VALUE - delta) { // Integer overflow.
throw connectionError(PROTOCOL_ERROR, "Flow control window overflowed for stream: %d", stream.id());
}
window += delta;
processedWindow += delta;

View File

@ -17,9 +17,11 @@ package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static java.lang.Math.max;
import static java.lang.Math.min;
@ -224,7 +226,7 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
private OutboundFlowState stateOrFail(int streamId) throws Http2Exception {
OutboundFlowState state = state(streamId);
if (state == null) {
throw protocolError("Missing flow control window for stream: %d", streamId);
throw connectionError(PROTOCOL_ERROR, "Missing flow control window for stream: %d", streamId);
}
return state;
}
@ -404,8 +406,8 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
*/
private int incrementStreamWindow(int delta) throws Http2Exception {
if (delta > 0 && Integer.MAX_VALUE - delta < window) {
throw new Http2StreamException(stream.id(), FLOW_CONTROL_ERROR, "Window size overflow for stream: "
+ stream.id());
throw streamError(stream.id(), FLOW_CONTROL_ERROR,
"Window size overflow for stream: %d", stream.id());
}
int previouslyStreamable = streamableBytes();
window += delta;
@ -476,7 +478,7 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
if (frame == null) {
break;
}
frame.writeError(Http2StreamException.format(stream.id(), INTERNAL_ERROR,
frame.writeError(streamError(stream.id(), INTERNAL_ERROR,
"Stream closed before write could take place"));
}
}

View File

@ -14,6 +14,7 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_ENCODING;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE;
@ -29,7 +30,6 @@ import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.util.CharsetUtil;
/**
* A HTTP2 frame listener that will decompress data frames according to the {@code content-encoding} header for each
@ -294,7 +294,7 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
throw e;
} catch (Throwable t) {
stream.setProperty(Http2Decompressor.class, copy);
throw new Http2Exception(Http2Error.INTERNAL_ERROR,
throw new Http2Exception(INTERNAL_ERROR,
"Error while returning bytes to flow control window", t);
}
}

View File

@ -37,6 +37,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";

View File

@ -17,9 +17,11 @@ package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.isStreamError;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
@ -27,6 +29,7 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.http2.Http2Exception.StreamException;
import java.util.Collection;
import java.util.List;
@ -120,10 +123,10 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
*/
public void onHttpClientUpgrade() throws Http2Exception {
if (connection().isServer()) {
throw protocolError("Client-side HTTP upgrade requested for a server");
throw connectionError(PROTOCOL_ERROR, "Client-side HTTP upgrade requested for a server");
}
if (prefaceSent || decoder.prefaceReceived()) {
throw protocolError("HTTP upgrade must occur before HTTP/2 preface is sent or received");
throw connectionError(PROTOCOL_ERROR, "HTTP upgrade must occur before HTTP/2 preface is sent or received");
}
// Create a local stream used for the HTTP cleartext upgrade.
@ -136,10 +139,10 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
*/
public void onHttpServerUpgrade(Http2Settings settings) throws Http2Exception {
if (!connection().isServer()) {
throw protocolError("Server-side HTTP upgrade requested for a client");
throw connectionError(PROTOCOL_ERROR, "Server-side HTTP upgrade requested for a client");
}
if (prefaceSent || decoder.prefaceReceived()) {
throw protocolError("HTTP upgrade must occur before HTTP/2 preface is sent or received");
throw connectionError(PROTOCOL_ERROR, "HTTP upgrade must occur before HTTP/2 preface is sent or received");
}
// Apply the settings but no ACK is necessary.
@ -274,8 +277,8 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
@Override
public void onException(ChannelHandlerContext ctx, Throwable cause) {
Http2Exception embedded = getEmbeddedHttp2Exception(cause);
if (embedded instanceof Http2StreamException) {
onStreamError(ctx, cause, (Http2StreamException) embedded);
if (isStreamError(embedded)) {
onStreamError(ctx, cause, (StreamException) embedded);
} else {
onConnectionError(ctx, cause, embedded);
}
@ -303,9 +306,9 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
*
* @param ctx the channel context
* @param cause the exception that was caught
* @param http2Ex the {@link Http2StreamException} that is embedded in the causality chain.
* @param http2Ex the {@link StreamException} that is embedded in the causality chain.
*/
protected void onStreamError(ChannelHandlerContext ctx, Throwable cause, Http2StreamException http2Ex) {
protected void onStreamError(ChannelHandlerContext ctx, Throwable cause, StreamException http2Ex) {
writeRstStream(ctx, http2Ex.streamId(), http2Ex.error().code(), ctx.newPromise());
}
@ -437,7 +440,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
// If the input so far doesn't match the preface, break the connection.
if (bytesRead == 0 || !prefaceSlice.equals(sourceSlice)) {
throw protocolError("HTTP/2 client preface string missing or corrupt.");
throw connectionError(PROTOCOL_ERROR, "HTTP/2 client preface string missing or corrupt.");
}
if (!clientPrefaceString.isReadable()) {

View File

@ -31,7 +31,8 @@ public enum Http2Error {
COMPRESSION_ERROR(0x9),
CONNECT_ERROR(0xA),
ENHANCE_YOUR_CALM(0xB),
INADEQUATE_SECURITY(0xC);
INADEQUATE_SECURITY(0xC),
HTTP_1_1_REQUIRED(0xD);
private final long code;

View File

@ -15,12 +15,13 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
/**
* Exception thrown when an HTTP2 error was encountered.
* Exception thrown when an HTTP/2 error was encountered.
*/
public class Http2Exception extends Exception {
private static final long serialVersionUID = -2292608019080068769L;
private static final long serialVersionUID = -6943456574080986447L;
private final Http2Error error;
public Http2Exception(Http2Error error) {
@ -41,15 +42,114 @@ public class Http2Exception extends Exception {
return error;
}
public static Http2Exception format(Http2Error error, String fmt, Object... args) {
/**
* Use if an error has occurred which can not be isolated to a single stream, but instead applies
* to the entire connection.
* @param error The type of error as defined by the HTTP/2 specification.
* @param fmt String with the content and format for the additional debug data.
* @param args Objects which fit into the format defined by {@code fmt}.
* @return An exception which can be translated into a HTTP/2 error.
*/
public static Http2Exception connectionError(Http2Error error, String fmt, Object... args) {
return new Http2Exception(error, String.format(fmt, args));
}
public static Http2Exception protocolError(String fmt, Object... args) {
return format(Http2Error.PROTOCOL_ERROR, fmt, args);
/**
* Use if an error has occurred which can not be isolated to a single stream, but instead applies
* to the entire connection.
* @param error The type of error as defined by the HTTP/2 specification.
* @param cause The object which caused the error.
* @param fmt String with the content and format for the additional debug data.
* @param args Objects which fit into the format defined by {@code fmt}.
* @return An exception which can be translated into a HTTP/2 error.
*/
public static Http2Exception connectionError(Http2Error error, Throwable cause,
String fmt, Object... args) {
return new Http2Exception(error, String.format(fmt, args), cause);
}
public static Http2Exception flowControlError(String fmt, Object... args) {
return format(Http2Error.FLOW_CONTROL_ERROR, fmt, args);
/**
* Use if an error which can be isolated to a single stream has occurred. If the {@code id} is not
* {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
* Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
* @param id The stream id for which the error is isolated to.
* @param error The type of error as defined by the HTTP/2 specification.
* @param fmt String with the content and format for the additional debug data.
* @param args Objects which fit into the format defined by {@code fmt}.
* @return If the {@code id} is not
* {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
* Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
*/
public static Http2Exception streamError(int id, Http2Error error, String fmt, Object... args) {
return CONNECTION_STREAM_ID == id ?
Http2Exception.connectionError(error, fmt, args) :
new StreamException(id, error, String.format(fmt, args));
}
/**
* Use if an error which can be isolated to a single stream has occurred. If the {@code id} is not
* {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
* Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
* @param id The stream id for which the error is isolated to.
* @param error The type of error as defined by the HTTP/2 specification.
* @param cause The object which caused the error.
* @param fmt String with the content and format for the additional debug data.
* @param args Objects which fit into the format defined by {@code fmt}.
* @return If the {@code id} is not
* {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
* Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
*/
public static Http2Exception streamError(int id, Http2Error error, Throwable cause,
String fmt, Object... args) {
return CONNECTION_STREAM_ID == id ?
Http2Exception.connectionError(error, cause, fmt, args) :
new StreamException(id, error, String.format(fmt, args), cause);
}
/**
* Check if an exception is isolated to a single stream or the entire connection.
* @param e The exception to check.
* @return {@code true} if {@code e} is an instance of {@link Http2Exception.StreamException}.
* {@code false} otherwise.
*/
public static boolean isStreamError(Http2Exception e) {
return e instanceof StreamException;
}
/**
* Get the stream id associated with an exception.
* @param e The exception to get the stream id for.
* @return {@link Http2CodecUtil#CONNECTION_STREAM_ID} if {@code e} is a connection error.
* Otherwise the stream id associated with the stream error.
*/
public static int streamId(Http2Exception e) {
return isStreamError(e) ? ((StreamException) e).streamId() : CONNECTION_STREAM_ID;
}
/**
* Represents an exception that can be isolated to a single stream (as opposed to the entire connection).
*/
public static final class StreamException extends Http2Exception {
private static final long serialVersionUID = 462766352505067095L;
private final int streamId;
private StreamException(int streamId, Http2Error error, String message) {
super(error, message);
this.streamId = streamId;
}
private StreamException(int streamId, Http2Error error, String message, Throwable cause) {
super(error, message, cause);
this.streamId = streamId;
}
private StreamException(int streamId, Http2Error error) {
super(error);
this.streamId = streamId;
}
public int streamId() {
return streamId;
}
}
}

View File

@ -156,7 +156,12 @@ public interface Http2FrameListener {
/**
* Handles an inbound PUSH_PROMISE frame. Only called if END_HEADERS encountered.
*
* <p>
* Promised requests MUST be cacheable
* (see <a href="https://tools.ietf.org/html/rfc7231#section-4.2.3">[RFC7231], Section 4.2.3</a>) and
* MUST be safe (see <a href="https://tools.ietf.org/html/rfc7231#section-4.2.1">[RFC7231], Section 4.2.1</a>).
* If these conditions do not hold the application MUST throw a {@link Http2Exception.StreamException} with
* error type {@link Http2Error#PROTOCOL_ERROR}.
* <p>
* Only one of the following methods will be called for each HEADERS frame sequence.
* One will be called when the END_HEADERS flag has been received.

View File

@ -14,6 +14,8 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
/**
* This exception is thrown when there are no more stream IDs available for the current connection
*/
@ -22,10 +24,10 @@ public class Http2NoMoreStreamIdsException extends Http2Exception {
private static final String ERROR_MESSAGE = "No more streams can be created on this connection";
public Http2NoMoreStreamIdsException() {
super(Http2Error.PROTOCOL_ERROR, ERROR_MESSAGE);
super(PROTOCOL_ERROR, ERROR_MESSAGE);
}
public Http2NoMoreStreamIdsException(Throwable cause) {
super(Http2Error.PROTOCOL_ERROR, ERROR_MESSAGE, cause);
super(PROTOCOL_ERROR, ERROR_MESSAGE, cause);
}
}

View File

@ -87,7 +87,12 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
try {
// Decode the HTTP2-Settings header and set the settings on the handler to make
// sure everything is fine with the request.
settings = decodeSettingsHeader(ctx, upgradeRequest.headers().get(HTTP_UPGRADE_SETTINGS_HEADER));
List<CharSequence> upgradeHeaders = upgradeRequest.headers().getAll(HTTP_UPGRADE_SETTINGS_HEADER);
if (upgradeHeaders.isEmpty() || upgradeHeaders.size() > 1) {
throw new IllegalArgumentException("There must be 1 and only 1 "
+ HTTP_UPGRADE_SETTINGS_HEADER + " header.");
}
settings = decodeSettingsHeader(ctx, upgradeHeaders.get(0));
connectionHandler.onHttpServerUpgrade(settings);
// Everything looks good, no need to modify the response.
} catch (Throwable e) {

View File

@ -1,49 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.handler.codec.http2;
public class Http2StreamException extends Http2Exception {
private static final long serialVersionUID = -7658235659648480024L;
private final int streamId;
public Http2StreamException(int streamId, Http2Error error, String message) {
super(error, message);
this.streamId = streamId;
}
public Http2StreamException(int streamId, Http2Error error, String message, Throwable cause) {
super(error, message, cause);
this.streamId = streamId;
}
public Http2StreamException(int streamId, Http2Error error) {
super(error);
this.streamId = streamId;
}
public int streamId() {
return streamId;
}
public static Http2StreamException format(int id, Http2Error error, String fmt, Object... args) {
return new Http2StreamException(id, error, String.format(fmt, args));
}
public static Http2StreamException streamClosedError(int id, String fmt, Object... args) {
return format(id, Http2Error.STREAM_CLOSED, fmt, args);
}
}

View File

@ -65,30 +65,32 @@ public class HttpToHttp2ConnectionHandler extends Http2ConnectionHandler {
if (msg instanceof FullHttpMessage) {
FullHttpMessage httpMsg = (FullHttpMessage) msg;
boolean hasData = httpMsg.content().isReadable();
// Provide the user the opportunity to specify the streamId
int streamId = 0;
boolean httpMsgNeedRelease = true;
try {
streamId = getStreamId(httpMsg.headers());
} catch (Exception e) {
httpMsg.release();
promise.setFailure(e);
return;
}
// Provide the user the opportunity to specify the streamId
int streamId = getStreamId(httpMsg.headers());
// Convert and write the headers.
Http2Headers http2Headers = HttpUtil.toHttp2Headers(httpMsg);
Http2ConnectionEncoder encoder = encoder();
// Convert and write the headers.
Http2Headers http2Headers = HttpUtil.toHttp2Headers(httpMsg);
Http2ConnectionEncoder encoder = encoder();
if (hasData) {
ChannelPromiseAggregator promiseAggregator = new ChannelPromiseAggregator(promise);
ChannelPromise headerPromise = ctx.newPromise();
ChannelPromise dataPromise = ctx.newPromise();
promiseAggregator.add(headerPromise, dataPromise);
encoder.writeHeaders(ctx, streamId, http2Headers, 0, false, headerPromise);
encoder.writeData(ctx, streamId, httpMsg.content(), 0, true, dataPromise);
} else {
encoder.writeHeaders(ctx, streamId, http2Headers, 0, true, promise);
if (hasData) {
ChannelPromiseAggregator promiseAggregator = new ChannelPromiseAggregator(promise);
ChannelPromise headerPromise = ctx.newPromise();
ChannelPromise dataPromise = ctx.newPromise();
promiseAggregator.add(headerPromise, dataPromise);
encoder.writeHeaders(ctx, streamId, http2Headers, 0, false, headerPromise);
httpMsgNeedRelease = false;
encoder.writeData(ctx, streamId, httpMsg.content(), 0, true, dataPromise);
} else {
encoder.writeHeaders(ctx, streamId, http2Headers, 0, true, promise);
}
} catch (Throwable t) {
promise.tryFailure(t);
} finally {
if (httpMsgNeedRelease) {
httpMsg.release();
}
}
} else {
ctx.write(msg, promise);

View File

@ -14,6 +14,9 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.BinaryHeaders;
@ -31,7 +34,6 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.internal.PlatformDependent;
import java.net.URI;
import java.util.HashMap;
@ -169,12 +171,12 @@ public final class HttpUtil {
try {
result = HttpResponseStatus.parseLine(status);
if (result == HttpResponseStatus.SWITCHING_PROTOCOLS) {
throw Http2Exception.protocolError("Invalid HTTP/2 status code '%d'", result.code());
throw connectionError(PROTOCOL_ERROR, "Invalid HTTP/2 status code '%d'", result.code());
}
} catch (Http2Exception e) {
throw e;
} catch (Exception ignored) {
throw Http2Exception.protocolError(
throw connectionError(PROTOCOL_ERROR,
"Unrecognized HTTP status code '%s' encountered in translation to HTTP/1.x", status);
}
return result;
@ -241,14 +243,13 @@ public final class HttpUtil {
FullHttpMessage destinationMessage, boolean addToTrailer) throws Http2Exception {
HttpHeaders headers = addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers();
boolean request = destinationMessage instanceof HttpRequest;
Http2ToHttpHeaderTranslator visitor = new Http2ToHttpHeaderTranslator(headers, request);
Http2ToHttpHeaderTranslator visitor = new Http2ToHttpHeaderTranslator(streamId, headers, request);
try {
sourceHeaders.forEachEntry(visitor);
} catch (Http2Exception ex) {
throw ex;
} catch (Exception ex) {
throw new Http2StreamException(streamId, Http2Error.PROTOCOL_ERROR,
"HTTP/2 to HTTP/1.x headers conversion error", ex);
} catch (Throwable t) {
throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
}
headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
@ -262,7 +263,7 @@ public final class HttpUtil {
/**
* Converts the given HTTP/1.x headers into HTTP/2 headers.
*/
public static Http2Headers toHttp2Headers(FullHttpMessage in) {
public static Http2Headers toHttp2Headers(FullHttpMessage in) throws Exception {
final Http2Headers out = new DefaultHttp2Headers();
HttpHeaders inHeaders = in.headers();
if (in instanceof HttpRequest) {
@ -301,21 +302,17 @@ public final class HttpUtil {
}
// Add the HTTP headers which have not been consumed above
try {
inHeaders.forEachEntry(new EntryVisitor() {
@Override
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
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);
}
return true;
inHeaders.forEachEntry(new EntryVisitor() {
@Override
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
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);
}
});
} catch (Exception ex) {
PlatformDependent.throwException(ex);
}
return true;
}
});
return out;
}
@ -340,6 +337,7 @@ public final class HttpUtil {
ExtensionHeaderNames.PATH.text());
}
private final int streamId;
private final HttpHeaders output;
private final Map<AsciiString, AsciiString> translations;
@ -350,7 +348,8 @@ public final class HttpUtil {
* @param request if {@code true}, translates headers using the request translation map. Otherwise uses the
* response translation map.
*/
Http2ToHttpHeaderTranslator(HttpHeaders output, boolean request) {
Http2ToHttpHeaderTranslator(int streamId, HttpHeaders output, boolean request) {
this.streamId = streamId;
this.output = output;
translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS;
}
@ -368,9 +367,8 @@ public final class HttpUtil {
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.2.3
// All headers that start with ':' are only valid in HTTP/2 context
if (translatedName.isEmpty() || translatedName.charAt(0) == ':') {
throw Http2Exception
.protocolError("Unknown HTTP/2 header '%s' encountered in translation to HTTP/1.x",
translatedName);
throw streamError(streamId, PROTOCOL_ERROR,
"Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", translatedName);
} else {
output.add(translatedName, value);
}

View File

@ -14,6 +14,9 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.TooLongFrameException;
@ -261,13 +264,13 @@ public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
throws Http2Exception {
FullHttpMessage msg = messageMap.get(streamId);
if (msg == null) {
throw Http2Exception.protocolError("Data Frame recieved for unknown stream id %d", streamId);
throw connectionError(PROTOCOL_ERROR, "Data Frame recieved for unknown stream id %d", streamId);
}
ByteBuf content = msg.content();
final int dataReadableBytes = data.readableBytes();
if (content.readableBytes() > maxContentLength - dataReadableBytes) {
throw Http2Exception.format(Http2Error.INTERNAL_ERROR,
throw connectionError(INTERNAL_ERROR,
"Content length exceeded max of %d for stream id %d", maxContentLength, streamId);
}
@ -313,7 +316,7 @@ public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
// A push promise should not be allowed to add headers to an existing stream
FullHttpMessage msg = processHeadersBegin(ctx, promisedStreamId, headers, false, false, false);
if (msg == null) {
throw Http2Exception.protocolError("Push Promise Frame recieved for pre-existing stream id %d",
throw connectionError(PROTOCOL_ERROR, "Push Promise Frame recieved for pre-existing stream id %d",
promisedStreamId);
}

View File

@ -14,6 +14,8 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import java.util.Map.Entry;
import io.netty.channel.ChannelHandlerContext;
@ -210,7 +212,7 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA
if (msg == null) {
HttpHeaders httpHeaders = outOfMessageFlowHeaders.remove(streamId);
if (httpHeaders == null) {
throw Http2Exception.protocolError("Priority Frame recieved for unknown stream id %d", streamId);
throw connectionError(PROTOCOL_ERROR, "Priority Frame recieved for unknown stream id %d", streamId);
}
Http2Headers http2Headers = new DefaultHttp2Headers();

View File

@ -19,7 +19,7 @@ import static io.netty.buffer.Unpooled.wrappedBuffer;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
import static io.netty.handler.codec.http2.Http2CodecUtil.emptyPingBuf;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
import static io.netty.util.CharsetUtil.UTF_8;
@ -183,7 +183,7 @@ public class DefaultHttp2ConnectionDecoderTest {
}
}
@Test(expected = Http2StreamException.class)
@Test(expected = Http2Exception.class)
public void dataReadForStreamInInvalidStateShouldThrow() throws Exception {
// Throw an exception when checking stream state.
when(stream.state()).thenReturn(Http2Stream.State.CLOSED);
@ -371,7 +371,7 @@ public class DefaultHttp2ConnectionDecoderTest {
@Test(expected = Http2Exception.class)
public void windowUpdateReadForUnknownStreamShouldThrow() throws Exception {
when(connection.requireStream(5)).thenThrow(protocolError(""));
when(connection.requireStream(5)).thenThrow(connectionError(PROTOCOL_ERROR, ""));
decode().onWindowUpdateRead(ctx, 5, 10);
}
@ -392,7 +392,7 @@ public class DefaultHttp2ConnectionDecoderTest {
@Test(expected = Http2Exception.class)
public void rstStreamReadForUnknownStreamShouldThrow() throws Exception {
when(connection.requireStream(5)).thenThrow(protocolError(""));
when(connection.requireStream(5)).thenThrow(connectionError(PROTOCOL_ERROR, ""));
decode().onRstStreamRead(ctx, 5, PROTOCOL_ERROR.code());
}

View File

@ -15,6 +15,7 @@
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2TestUtil.as;
import static io.netty.handler.codec.http2.Http2TestUtil.randomString;
import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel;
@ -137,7 +138,7 @@ public class Http2ConnectionRoundtripTest {
clientChannel.pipeline().addFirst(new ChannelHandlerAdapter() {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
throw Http2Exception.protocolError("Fake Exception");
throw Http2Exception.connectionError(PROTOCOL_ERROR, "Fake Exception");
}
});

View File

@ -14,6 +14,24 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static io.netty.handler.codec.http2.Http2CodecUtil.ignoreSettingsHandler;
import static io.netty.handler.codec.http2.Http2TestUtil.as;
import static io.netty.util.CharsetUtil.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyShort;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
@ -31,16 +49,9 @@ import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http2.Http2TestUtil.FrameCountDown;
import io.netty.util.NetUtil;
import io.netty.util.concurrent.Future;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@ -48,14 +59,13 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static io.netty.handler.codec.http.HttpMethod.*;
import static io.netty.handler.codec.http.HttpVersion.*;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static io.netty.handler.codec.http2.Http2TestUtil.*;
import static io.netty.util.CharsetUtil.*;
import static java.util.concurrent.TimeUnit.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Testing the {@link HttpToHttp2ConnectionHandler} for {@link FullHttpRequest} objects into HTTP/2 frames
@ -95,42 +105,38 @@ public class HttpToHttp2ConnectionHandlerTest {
public void testJustHeadersRequest() throws Exception {
bootstrapEnv(3);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/example");
try {
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST,
"http://my-user_name@www.example.org:5555/example");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "www.example.org:5555");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2");
httpHeaders.add("foo2", "goo2");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(as("GET")).path(as("/example"))
.authority(as("www.example.org:5555")).scheme(as("http"))
.add(as("foo"), as("goo")).add(as("foo"), as("goo2"))
.add(as("foo2"), as("goo2"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST,
"http://my-user_name@www.example.org:5555/example");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "www.example.org:5555");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2");
httpHeaders.add("foo2", "goo2");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(as("GET")).path(as("/example"))
.authority(as("www.example.org:5555")).scheme(as("http"))
.add(as("foo"), as("goo")).add(as("foo"), as("goo2"))
.add(as("foo2"), as("goo2"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
writePromise.awaitUninterruptibly(2, SECONDS);
assertTrue(writePromise.isSuccess());
writeFuture.awaitUninterruptibly(2, SECONDS);
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} finally {
request.release();
}
writePromise.awaitUninterruptibly(2, SECONDS);
assertTrue(writePromise.isSuccess());
writeFuture.awaitUninterruptibly(2, SECONDS);
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
assertEquals(0, request.refCnt());
}
@Test
public void testRequestWithBody() throws Exception {
final String text = "foooooogoooo";
final ByteBuf data = Unpooled.copiedBuffer(text, UTF_8);
final List<String> receivedBuffers = Collections.synchronizedList(new ArrayList<String>());
doAnswer(new Answer<Void>() {
@Override
@ -141,35 +147,33 @@ public class HttpToHttp2ConnectionHandlerTest {
}).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3),
any(ByteBuf.class), eq(0), eq(true));
bootstrapEnv(4);
try {
final HttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, "/example", data.retain());
final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "http://your_user-name123@www.example.org:5555/example");
httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2");
httpHeaders.add("foo2", "goo2");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(as("POST")).path(as("/example"))
.authority(as("www.example.org:5555")).scheme(as("http"))
.add(as("foo"), as("goo")).add(as("foo"), as("goo2"))
.add(as("foo2"), as("goo2"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, "/example",
Unpooled.copiedBuffer(text, UTF_8));
final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "http://your_user-name123@www.example.org:5555/example");
httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2");
httpHeaders.add("foo2", "goo2");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(as("POST")).path(as("/example"))
.authority(as("www.example.org:5555")).scheme(as("http"))
.add(as("foo"), as("goo")).add(as("foo"), as("goo2"))
.add(as("foo2"), as("goo2"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
writePromise.awaitUninterruptibly(2, SECONDS);
assertTrue(writePromise.isSuccess());
writeFuture.awaitUninterruptibly(2, SECONDS);
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2Headers), eq(0),
anyShort(), anyBoolean(), eq(0), eq(false));
verify(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), any(ByteBuf.class), eq(0),
eq(true));
assertEquals(1, receivedBuffers.size());
assertEquals(text, receivedBuffers.get(0));
} finally {
data.release();
}
writePromise.awaitUninterruptibly(2, SECONDS);
assertTrue(writePromise.isSuccess());
writeFuture.awaitUninterruptibly(2, SECONDS);
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2Headers), eq(0),
anyShort(), anyBoolean(), eq(0), eq(false));
verify(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), any(ByteBuf.class), eq(0),
eq(true));
assertEquals(1, receivedBuffers.size());
assertEquals(text, receivedBuffers.get(0));
assertEquals(0, request.refCnt());
}
private void bootstrapEnv(int requestCountDown) throws Exception {

View File

@ -14,6 +14,7 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Exception.isStreamError;
import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
import static io.netty.handler.codec.http2.Http2TestUtil.as;
import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel;
@ -243,7 +244,7 @@ public class InboundHttp2ToHttpAdapterTest {
}
});
awaitRequests();
assertTrue(serverException instanceof Http2StreamException);
assertTrue(isStreamError(serverException));
}
@Test