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:
parent
95540bd49e
commit
a8e5fb12fa
@ -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':
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user