2015-01-20 01:48:11 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
import io.netty.buffer.ByteBuf;
|
2015-05-11 21:10:23 +02:00
|
|
|
import io.netty.buffer.ByteBufUtil;
|
2015-01-20 01:48:11 +01:00
|
|
|
import io.netty.buffer.Unpooled;
|
2015-01-31 04:54:35 +01:00
|
|
|
import io.netty.channel.Channel;
|
2015-01-20 01:48:11 +01:00
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.channel.ChannelPromise;
|
2015-01-31 04:54:35 +01:00
|
|
|
import io.netty.channel.DefaultChannelPromise;
|
2015-06-01 09:05:00 +02:00
|
|
|
import io.netty.handler.ssl.ApplicationProtocolNames;
|
2015-09-11 18:56:00 +02:00
|
|
|
import io.netty.util.AsciiString;
|
2015-01-31 04:54:35 +01:00
|
|
|
import io.netty.util.concurrent.EventExecutor;
|
2016-04-12 14:22:41 +02:00
|
|
|
import io.netty.util.internal.UnstableApi;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-09-11 18:12:22 +02:00
|
|
|
import static io.netty.buffer.Unpooled.directBuffer;
|
|
|
|
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
2016-09-16 15:57:33 +02:00
|
|
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
2017-01-14 02:09:44 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
2016-11-23 20:32:41 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.headerListSizeError;
|
2015-09-11 18:12:22 +02:00
|
|
|
import static io.netty.util.CharsetUtil.UTF_8;
|
2015-12-18 00:28:30 +01:00
|
|
|
import static java.lang.Math.max;
|
|
|
|
import static java.lang.Math.min;
|
HTTP/2 Child Channel and FrameCodec Feature Parity.
Motivation:
This PR (unfortunately) does 4 things:
1) Add outbound flow control to the Http2MultiplexCodec:
The HTTP/2 child channel API should interact with HTTP/2 outbound/remote flow control. That is,
if a H2 stream used up all its flow control window, the corresponding child channel should be
marked unwritable and a writability-changed event should be fired. Similarly, a unwritable
child channel should be marked writable and a writability-event should be fired, once a
WINDOW_UPDATE frame has been received. The changes are (mostly) contained in ChannelOutboundBuffer,
AbstractHttp2StreamChannel and Http2MultiplexCodec.
2) Introduce a Http2Stream2 object, that is used instead of stream identifiers on stream frames. A
Http2Stream2 object allows an application to attach state to it, and so a application handler
no longer needs to maintain stream state (i.e. in a map(id -> state)) himself.
3) Remove stream state events, which are no longer necessary due to the introduction of Http2Stream2.
Also those stream state events have been found hard and complex to work with, when porting gRPC
to the Http2FrameCodec.
4) Add support for HTTP/2 frames that have not yet been implemented, like PING and SETTINGS. Also add
a Http2FrameCodecBuilder that exposes options from the Http2ConnectionHandler API that couldn't else
be used with the frame codec, like buffering outbound streams, window update ratio, frame logger, etc.
Modifications:
1) A child channel's writability and a H2 stream's outbound flow control window interact, as described
in the motivation. A channel handler is free to ignore the channel's writability, in which case the
parent channel is reponsible for buffering writes until a WINDOW_UPDATE is received.
The connection-level flow control window is ignored for now. That is, a child channel's writability
is only affected by the stream-level flow control window. So a child channel could be marked writable,
even though the connection-level flow control window is zero.
2) Modify Http2StreamFrame and the Http2FrameCodec to take a Http2Stream2 object intstead of a primitive
integer. Introduce a special Http2ChannelDuplexHandler that has newStream() and forEachActiveStream()
methods. It's recommended for a user to extend from this handler, to use those advanced features.
3) As explained in the documentation, a new inbound stream active can be detected by checking if the
Http2Stream2.managedState() of a Http2HeadersFrame is null. An outbound stream active can be detected
by adding a listener to the ChannelPromise of the write of the first Http2HeadersFrame. A stream
closed event can be listened to by adding a listener to the Http2Stream2.closeFuture().
4) Add a simple Http2FrameCodecBuilder and implement the missing frame types.
Result:
1) The Http2MultiplexCodec supports outbound flow control.
2) The Http2FrameCodec API makes it easy for a user to manage custom stream specific state and to create
new outbound streams.
3) The Http2FrameCodec API is much cleaner and easier to work with. Hacks like the ChannelCarryingHeadersFrame
are no longer necessary.
4) The Http2FrameCodec now also supports PING and SETTINGS frames. The Http2FrameCodecBuilder allows the Http2FrameCodec
to use some of the rich features of the Http2ConnectionHandler API.
2016-08-23 13:03:39 +02:00
|
|
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
|
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
2015-09-11 18:12:22 +02:00
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* Constants and utility method used for encoding/decoding HTTP2 frames.
|
|
|
|
*/
|
2016-04-12 14:22:41 +02:00
|
|
|
@UnstableApi
|
2015-01-20 01:48:11 +01:00
|
|
|
public final class Http2CodecUtil {
|
|
|
|
public static final int CONNECTION_STREAM_ID = 0;
|
|
|
|
public static final int HTTP_UPGRADE_STREAM_ID = 1;
|
2017-08-12 21:18:22 +02:00
|
|
|
public static final CharSequence HTTP_UPGRADE_SETTINGS_HEADER = AsciiString.cached("HTTP2-Settings");
|
2015-09-11 18:56:00 +02:00
|
|
|
public static final CharSequence HTTP_UPGRADE_PROTOCOL_NAME = "h2c";
|
|
|
|
public static final CharSequence TLS_UPGRADE_PROTOCOL_NAME = ApplicationProtocolNames.HTTP_2;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
|
2017-03-30 03:54:44 +02:00
|
|
|
public static final short MAX_UNSIGNED_BYTE = 0xff;
|
2016-06-23 14:09:23 +02:00
|
|
|
/**
|
|
|
|
* The maximum number of padding bytes. That is the 255 padding bytes appended to the end of a frame and the 1 byte
|
|
|
|
* pad length field.
|
|
|
|
*/
|
|
|
|
public static final int MAX_PADDING = 256;
|
2017-03-30 03:54:44 +02:00
|
|
|
public static final long MAX_UNSIGNED_INT = 0xffffffffL;
|
2015-01-20 01:48:11 +01:00
|
|
|
public static final int FRAME_HEADER_LENGTH = 9;
|
|
|
|
public static final int SETTING_ENTRY_LENGTH = 6;
|
|
|
|
public static final int PRIORITY_ENTRY_LENGTH = 5;
|
|
|
|
public static final int INT_FIELD_LENGTH = 4;
|
|
|
|
public static final short MAX_WEIGHT = 256;
|
|
|
|
public static final short MIN_WEIGHT = 1;
|
|
|
|
|
2016-04-13 15:25:15 +02:00
|
|
|
private static final ByteBuf CONNECTION_PREFACE =
|
|
|
|
unreleasableBuffer(directBuffer(24).writeBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(UTF_8)))
|
|
|
|
.asReadOnly();
|
2015-09-11 18:12:22 +02:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
private static final int MAX_PADDING_LENGTH_LENGTH = 1;
|
|
|
|
public static final int DATA_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
|
|
|
|
public static final int HEADERS_FRAME_HEADER_LENGTH =
|
|
|
|
FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH + 1;
|
|
|
|
public static final int PRIORITY_FRAME_LENGTH = FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH;
|
|
|
|
public static final int RST_STREAM_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
|
|
|
|
public static final int PUSH_PROMISE_FRAME_HEADER_LENGTH =
|
|
|
|
FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH;
|
|
|
|
public static final int GO_AWAY_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + 2 * INT_FIELD_LENGTH;
|
|
|
|
public static final int WINDOW_UPDATE_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
|
|
|
|
public static final int CONTINUATION_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
|
|
|
|
|
2015-04-10 18:13:32 +02:00
|
|
|
public static final char SETTINGS_HEADER_TABLE_SIZE = 1;
|
|
|
|
public static final char SETTINGS_ENABLE_PUSH = 2;
|
|
|
|
public static final char SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
|
|
|
public static final char SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
|
|
|
public static final char SETTINGS_MAX_FRAME_SIZE = 5;
|
|
|
|
public static final char SETTINGS_MAX_HEADER_LIST_SIZE = 6;
|
2015-04-01 16:51:11 +02:00
|
|
|
public static final int NUM_STANDARD_SETTINGS = 6;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2016-09-05 14:58:08 +02:00
|
|
|
public static final long MAX_HEADER_TABLE_SIZE = MAX_UNSIGNED_INT;
|
2015-01-20 01:48:11 +01:00
|
|
|
public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
|
|
|
|
public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
|
|
|
|
public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
|
2017-03-30 03:54:44 +02:00
|
|
|
public static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xffffff;
|
2016-09-16 15:57:33 +02:00
|
|
|
public static final long MAX_HEADER_LIST_SIZE = MAX_UNSIGNED_INT;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
public static final long MIN_HEADER_TABLE_SIZE = 0;
|
|
|
|
public static final long MIN_CONCURRENT_STREAMS = 0;
|
|
|
|
public static final int MIN_INITIAL_WINDOW_SIZE = 0;
|
|
|
|
public static final long MIN_HEADER_LIST_SIZE = 0;
|
|
|
|
|
|
|
|
public static final int DEFAULT_WINDOW_SIZE = 65535;
|
|
|
|
public static final short DEFAULT_PRIORITY_WEIGHT = 16;
|
|
|
|
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
|
2017-01-14 02:09:44 +01:00
|
|
|
/**
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">The initial value of this setting is unlimited</a>.
|
|
|
|
* However in practice we don't want to allow our peers to use unlimited memory by default. So we take advantage
|
|
|
|
* of the <q>For any given request, a lower limit than what is advertised MAY be enforced.</q> loophole.
|
|
|
|
*/
|
|
|
|
public static final long DEFAULT_HEADER_LIST_SIZE = 8192;
|
2015-01-20 01:48:11 +01:00
|
|
|
public static final int DEFAULT_MAX_FRAME_SIZE = MAX_FRAME_SIZE_LOWER_BOUND;
|
2017-01-24 21:50:39 +01:00
|
|
|
/**
|
|
|
|
* The assumed minimum value for {@code SETTINGS_MAX_CONCURRENT_STREAMS} as
|
|
|
|
* recommended by the <a herf="https://tools.ietf.org/html/rfc7540#section-6.5.2">HTTP/2 spec</a>.
|
|
|
|
*/
|
|
|
|
public static final int SMALLEST_MAX_CONCURRENT_STREAMS = 100;
|
|
|
|
static final int DEFAULT_MAX_RESERVED_STREAMS = SMALLEST_MAX_CONCURRENT_STREAMS;
|
|
|
|
static final int DEFAULT_MIN_ALLOCATION_CHUNK = 1024;
|
2017-06-12 21:37:57 +02:00
|
|
|
static final int DEFAULT_INITIAL_HUFFMAN_DECODE_CAPACITY = 32;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2017-01-21 00:14:42 +01:00
|
|
|
/**
|
|
|
|
* Calculate the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
|
|
|
|
* @param maxHeaderListSize
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> for the local
|
|
|
|
* endpoint.
|
|
|
|
* @return the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
|
|
|
|
*/
|
|
|
|
public static long calculateMaxHeaderListSizeGoAway(long maxHeaderListSize) {
|
|
|
|
// This is equivalent to `maxHeaderListSize * 1.25` but we avoid floating point multiplication.
|
|
|
|
return maxHeaderListSize + (maxHeaderListSize >>> 2);
|
|
|
|
}
|
|
|
|
|
HTTP/2 Child Channel and FrameCodec Feature Parity.
Motivation:
This PR (unfortunately) does 4 things:
1) Add outbound flow control to the Http2MultiplexCodec:
The HTTP/2 child channel API should interact with HTTP/2 outbound/remote flow control. That is,
if a H2 stream used up all its flow control window, the corresponding child channel should be
marked unwritable and a writability-changed event should be fired. Similarly, a unwritable
child channel should be marked writable and a writability-event should be fired, once a
WINDOW_UPDATE frame has been received. The changes are (mostly) contained in ChannelOutboundBuffer,
AbstractHttp2StreamChannel and Http2MultiplexCodec.
2) Introduce a Http2Stream2 object, that is used instead of stream identifiers on stream frames. A
Http2Stream2 object allows an application to attach state to it, and so a application handler
no longer needs to maintain stream state (i.e. in a map(id -> state)) himself.
3) Remove stream state events, which are no longer necessary due to the introduction of Http2Stream2.
Also those stream state events have been found hard and complex to work with, when porting gRPC
to the Http2FrameCodec.
4) Add support for HTTP/2 frames that have not yet been implemented, like PING and SETTINGS. Also add
a Http2FrameCodecBuilder that exposes options from the Http2ConnectionHandler API that couldn't else
be used with the frame codec, like buffering outbound streams, window update ratio, frame logger, etc.
Modifications:
1) A child channel's writability and a H2 stream's outbound flow control window interact, as described
in the motivation. A channel handler is free to ignore the channel's writability, in which case the
parent channel is reponsible for buffering writes until a WINDOW_UPDATE is received.
The connection-level flow control window is ignored for now. That is, a child channel's writability
is only affected by the stream-level flow control window. So a child channel could be marked writable,
even though the connection-level flow control window is zero.
2) Modify Http2StreamFrame and the Http2FrameCodec to take a Http2Stream2 object intstead of a primitive
integer. Introduce a special Http2ChannelDuplexHandler that has newStream() and forEachActiveStream()
methods. It's recommended for a user to extend from this handler, to use those advanced features.
3) As explained in the documentation, a new inbound stream active can be detected by checking if the
Http2Stream2.managedState() of a Http2HeadersFrame is null. An outbound stream active can be detected
by adding a listener to the ChannelPromise of the write of the first Http2HeadersFrame. A stream
closed event can be listened to by adding a listener to the Http2Stream2.closeFuture().
4) Add a simple Http2FrameCodecBuilder and implement the missing frame types.
Result:
1) The Http2MultiplexCodec supports outbound flow control.
2) The Http2FrameCodec API makes it easy for a user to manage custom stream specific state and to create
new outbound streams.
3) The Http2FrameCodec API is much cleaner and easier to work with. Hacks like the ChannelCarryingHeadersFrame
are no longer necessary.
4) The Http2FrameCodec now also supports PING and SETTINGS frames. The Http2FrameCodecBuilder allows the Http2FrameCodec
to use some of the rich features of the Http2ConnectionHandler API.
2016-08-23 13:03:39 +02:00
|
|
|
public static final long DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_MILLIS = MILLISECONDS.convert(30, SECONDS);
|
|
|
|
|
2016-08-16 15:22:39 +02:00
|
|
|
/**
|
|
|
|
* Returns {@code true} if the stream is an outbound stream.
|
|
|
|
*
|
|
|
|
* @param server {@code true} if the endpoint is a server, {@code false} otherwise.
|
|
|
|
* @param streamId the stream identifier
|
|
|
|
*/
|
|
|
|
public static boolean isOutboundStream(boolean server, int streamId) {
|
|
|
|
boolean even = (streamId & 1) == 0;
|
|
|
|
return streamId > 0 && server == even;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the {@code streamId} is a valid HTTP/2 stream identifier.
|
|
|
|
*/
|
|
|
|
public static boolean isStreamIdValid(int streamId) {
|
|
|
|
return streamId >= 0;
|
|
|
|
}
|
|
|
|
|
2019-06-27 21:43:31 +02:00
|
|
|
static boolean isStreamIdValid(int streamId, boolean server) {
|
|
|
|
return isStreamIdValid(streamId) && server == ((streamId & 1) == 0);
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* Indicates whether or not the given value for max frame size falls within the valid range.
|
|
|
|
*/
|
|
|
|
public static boolean isMaxFrameSizeValid(int maxFrameSize) {
|
|
|
|
return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-02-14 02:41:49 +01:00
|
|
|
* Returns a buffer containing the {@link #CONNECTION_PREFACE}.
|
2015-01-20 01:48:11 +01:00
|
|
|
*/
|
|
|
|
public static ByteBuf connectionPrefaceBuf() {
|
|
|
|
// Return a duplicate so that modifications to the reader index will not affect the original buffer.
|
2016-04-14 10:31:48 +02:00
|
|
|
return CONNECTION_PREFACE.retainedDuplicate();
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-19 22:37:03 +02:00
|
|
|
* Iteratively looks through the causality chain for the given exception and returns the first
|
2015-01-20 01:48:11 +01:00
|
|
|
* {@link Http2Exception} or {@code null} if none.
|
|
|
|
*/
|
|
|
|
public static Http2Exception getEmbeddedHttp2Exception(Throwable cause) {
|
|
|
|
while (cause != null) {
|
|
|
|
if (cause instanceof Http2Exception) {
|
|
|
|
return (Http2Exception) cause;
|
|
|
|
}
|
|
|
|
cause = cause.getCause();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a buffer containing the error message from the given exception. If the cause is
|
|
|
|
* {@code null} returns an empty buffer.
|
|
|
|
*/
|
|
|
|
public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
|
if (cause == null || cause.getMessage() == null) {
|
|
|
|
return Unpooled.EMPTY_BUFFER;
|
|
|
|
}
|
|
|
|
|
2016-03-05 02:08:40 +01:00
|
|
|
return ByteBufUtil.writeUtf8(ctx.alloc(), cause.getMessage());
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads a big-endian (31-bit) integer from the buffer.
|
|
|
|
*/
|
|
|
|
public static int readUnsignedInt(ByteBuf buf) {
|
2017-03-30 03:54:44 +02:00
|
|
|
return buf.readInt() & 0x7fffffff;
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes an HTTP/2 frame header to the output buffer.
|
|
|
|
*/
|
|
|
|
public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
|
|
|
|
Http2Flags flags, int streamId) {
|
|
|
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
2015-01-31 04:54:35 +01:00
|
|
|
writeFrameHeaderInternal(out, payloadLength, type, flags, streamId);
|
|
|
|
}
|
|
|
|
|
2015-12-18 00:28:30 +01:00
|
|
|
/**
|
|
|
|
* Calculate the amount of bytes that can be sent by {@code state}. The lower bound is {@code 0}.
|
|
|
|
*/
|
|
|
|
public static int streamableBytes(StreamByteDistributor.StreamState state) {
|
2017-12-20 17:55:15 +01:00
|
|
|
return max(0, (int) min(state.pendingBytes(), state.windowSize()));
|
2015-12-18 00:28:30 +01:00
|
|
|
}
|
|
|
|
|
2017-01-14 02:09:44 +01:00
|
|
|
/**
|
|
|
|
* Results in a RST_STREAM being sent for {@code streamId} due to violating
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
|
|
|
|
* @param streamId The stream ID that was being processed when the exceptional condition occurred.
|
|
|
|
* @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
|
|
|
|
* @param onDecode {@code true} if the exception was encountered during decoder. {@code false} for encode.
|
|
|
|
* @throws Http2Exception a stream error.
|
|
|
|
*/
|
2016-11-23 20:32:41 +01:00
|
|
|
public static void headerListSizeExceeded(int streamId, long maxHeaderListSize,
|
|
|
|
boolean onDecode) throws Http2Exception {
|
|
|
|
throw headerListSizeError(streamId, PROTOCOL_ERROR, onDecode, "Header size exceeded max " +
|
|
|
|
"allowed size (%d)", maxHeaderListSize);
|
2016-09-16 15:57:33 +02:00
|
|
|
}
|
|
|
|
|
2017-01-14 02:09:44 +01:00
|
|
|
/**
|
|
|
|
* Results in a GO_AWAY being sent due to violating
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> in an unrecoverable
|
|
|
|
* manner.
|
|
|
|
* @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
|
|
|
|
* @throws Http2Exception a connection error.
|
|
|
|
*/
|
|
|
|
public static void headerListSizeExceeded(long maxHeaderListSize) throws Http2Exception {
|
|
|
|
throw connectionError(PROTOCOL_ERROR, "Header size exceeded max " +
|
|
|
|
"allowed size (%d)", maxHeaderListSize);
|
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
static void writeFrameHeaderInternal(ByteBuf out, int payloadLength, byte type,
|
|
|
|
Http2Flags flags, int streamId) {
|
2015-01-20 01:48:11 +01:00
|
|
|
out.writeMedium(payloadLength);
|
|
|
|
out.writeByte(type);
|
|
|
|
out.writeByte(flags.value());
|
|
|
|
out.writeInt(streamId);
|
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
/**
|
|
|
|
* Provides the ability to associate the outcome of multiple {@link ChannelPromise}
|
|
|
|
* objects into a single {@link ChannelPromise} object.
|
|
|
|
*/
|
2016-03-07 19:05:22 +01:00
|
|
|
static final class SimpleChannelPromiseAggregator extends DefaultChannelPromise {
|
2015-01-31 04:54:35 +01:00
|
|
|
private final ChannelPromise promise;
|
|
|
|
private int expectedCount;
|
2016-03-07 19:05:22 +01:00
|
|
|
private int doneCount;
|
|
|
|
private Throwable lastFailure;
|
2015-01-31 04:54:35 +01:00
|
|
|
private boolean doneAllocating;
|
|
|
|
|
|
|
|
SimpleChannelPromiseAggregator(ChannelPromise promise, Channel c, EventExecutor e) {
|
|
|
|
super(c, e);
|
2016-03-07 19:05:22 +01:00
|
|
|
assert promise != null && !promise.isDone();
|
2015-01-31 04:54:35 +01:00
|
|
|
this.promise = promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allocate a new promise which will be used to aggregate the overall success of this promise aggregator.
|
|
|
|
* @return A new promise which will be aggregated.
|
|
|
|
* {@code null} if {@link #doneAllocatingPromises()} was previously called.
|
|
|
|
*/
|
|
|
|
public ChannelPromise newPromise() {
|
2016-03-07 19:05:22 +01:00
|
|
|
assert !doneAllocating : "Done allocating. No more promises can be allocated.";
|
2015-01-31 04:54:35 +01:00
|
|
|
++expectedCount;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Signify that no more {@link #newPromise()} allocations will be made.
|
|
|
|
* The aggregation can not be successful until this method is called.
|
|
|
|
* @return The promise that is the aggregation of all promises allocated with {@link #newPromise()}.
|
|
|
|
*/
|
|
|
|
public ChannelPromise doneAllocatingPromises() {
|
|
|
|
if (!doneAllocating) {
|
|
|
|
doneAllocating = true;
|
2016-03-07 19:05:22 +01:00
|
|
|
if (doneCount == expectedCount || expectedCount == 0) {
|
|
|
|
return setPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean tryFailure(Throwable cause) {
|
2015-08-29 01:29:47 +02:00
|
|
|
if (allowFailure()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
lastFailure = cause;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
return tryPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
// TODO: We break the interface a bit here.
|
|
|
|
// Multiple failure events can be processed without issue because this is an aggregation.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fail this object if it has not already been failed.
|
|
|
|
* <p>
|
|
|
|
* This method will NOT throw an {@link IllegalStateException} if called multiple times
|
|
|
|
* because that may be expected.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public ChannelPromise setFailure(Throwable cause) {
|
2015-08-29 01:29:47 +02:00
|
|
|
if (allowFailure()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
lastFailure = cause;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
return setPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelPromise setSuccess(Void result) {
|
2015-05-05 00:18:12 +02:00
|
|
|
if (awaitingPromises()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
setPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean trySuccess(Void result) {
|
2015-05-05 00:18:12 +02:00
|
|
|
if (awaitingPromises()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
return tryPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
// TODO: We break the interface a bit here.
|
|
|
|
// Multiple success events can be processed without issue because this is an aggregation.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2016-03-07 19:05:22 +01:00
|
|
|
|
|
|
|
private boolean allowFailure() {
|
|
|
|
return awaitingPromises() || expectedCount == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean awaitingPromises() {
|
|
|
|
return doneCount < expectedCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean allPromisesDone() {
|
|
|
|
return doneCount == expectedCount && doneAllocating;
|
|
|
|
}
|
|
|
|
|
|
|
|
private ChannelPromise setPromise() {
|
|
|
|
if (lastFailure == null) {
|
|
|
|
promise.setSuccess();
|
|
|
|
return super.setSuccess(null);
|
|
|
|
} else {
|
|
|
|
promise.setFailure(lastFailure);
|
|
|
|
return super.setFailure(lastFailure);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean tryPromise() {
|
|
|
|
if (lastFailure == null) {
|
|
|
|
promise.trySuccess();
|
|
|
|
return super.trySuccess(null);
|
|
|
|
} else {
|
|
|
|
promise.tryFailure(lastFailure);
|
|
|
|
return super.tryFailure(lastFailure);
|
|
|
|
}
|
|
|
|
}
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
|
2016-06-23 14:09:23 +02:00
|
|
|
public static void verifyPadding(int padding) {
|
|
|
|
if (padding < 0 || padding > MAX_PADDING) {
|
|
|
|
throw new IllegalArgumentException(String.format("Invalid padding '%d'. Padding must be between 0 and " +
|
|
|
|
"%d (inclusive).", padding, MAX_PADDING));
|
|
|
|
}
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
private Http2CodecUtil() { }
|
|
|
|
}
|