/*
* Copyright 2015 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.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
import io.netty.util.internal.UnstableApi;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_INITIAL_HUFFMAN_DECODE_CAPACITY;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_RESERVED_STREAMS;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
Abstract base class which defines commonly used features required to build Http2ConnectionHandler
instances. Three ways to build a Http2ConnectionHandler
Let the builder create a Http2ConnectionHandler
Simply call all the necessary setter methods, and then use build()
to build a new Http2ConnectionHandler
. Setting the following properties are prohibited because they are used for other ways of building a Http2ConnectionHandler
. conflicts with this option:
Let the builder use the Http2ConnectionHandler
you specified
Call connection(Http2Connection)
to tell the builder that you want to build the handler from the Http2Connection
you specified. Setting the following properties are prohibited and thus will trigger an IllegalStateException
because they conflict with this option.
Let the builder use the Http2ConnectionDecoder
and Http2ConnectionEncoder
you specified
Call codec(Http2ConnectionDecoder, Http2ConnectionEncoder)
to tell the builder that you want to built the handler from the Http2ConnectionDecoder
and Http2ConnectionEncoder
you specified. Setting the following properties are prohibited and thus will trigger an IllegalStateException
because they conflict with this option:
server(boolean)
connection(Http2Connection)
frameLogger(Http2FrameLogger)
headerSensitivityDetector(SensitivityDetector)
encoderEnforceMaxConcurrentStreams(boolean)
encoderIgnoreMaxHeaderListSize(boolean)
initialHuffmanDecodeCapacity(int)
Exposing necessary methods in a subclass
build()
method and all property access methods are protected
. Choose the methods to expose to the users of your builder implementation and make them public
. Type parameters:
/**
* Abstract base class which defines commonly used features required to build {@link Http2ConnectionHandler} instances.
*
* <h3>Three ways to build a {@link Http2ConnectionHandler}</h3>
* <h4>Let the builder create a {@link Http2ConnectionHandler}</h4>
* Simply call all the necessary setter methods, and then use {@link #build()} to build a new
* {@link Http2ConnectionHandler}. Setting the following properties are prohibited because they are used for
* other ways of building a {@link Http2ConnectionHandler}.
* conflicts with this option:
* <ul>
* <li>{@link #connection(Http2Connection)}</li>
* <li>{@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)}</li>
* </ul>
*
*
* <h4>Let the builder use the {@link Http2ConnectionHandler} you specified</h4>
* Call {@link #connection(Http2Connection)} to tell the builder that you want to build the handler from the
* {@link Http2Connection} you specified. Setting the following properties are prohibited and thus will trigger
* an {@link IllegalStateException} because they conflict with this option.
* <ul>
* <li>{@link #server(boolean)}</li>
* <li>{@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)}</li>
* </ul>
*
* <h4>Let the builder use the {@link Http2ConnectionDecoder} and {@link Http2ConnectionEncoder} you specified</h4>
* Call {@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)} to tell the builder that you want to built the
* handler from the {@link Http2ConnectionDecoder} and {@link Http2ConnectionEncoder} you specified. Setting the
* following properties are prohibited and thus will trigger an {@link IllegalStateException} because they conflict
* with this option:
* <ul>
* <li>{@link #server(boolean)}</li>
* <li>{@link #connection(Http2Connection)}</li>
* <li>{@link #frameLogger(Http2FrameLogger)}</li>
* <li>{@link #headerSensitivityDetector(SensitivityDetector)}</li>
* <li>{@link #encoderEnforceMaxConcurrentStreams(boolean)}</li>
* <li>{@link #encoderIgnoreMaxHeaderListSize(boolean)}</li>
* <li>{@link #initialHuffmanDecodeCapacity(int)}</li>
* </ul>
*
* <h3>Exposing necessary methods in a subclass</h3>
* {@link #build()} method and all property access methods are {@code protected}. Choose the methods to expose to the
* users of your builder implementation and make them {@code public}.
*
* @param <T> The type of handler created by this builder.
* @param <B> The concrete type of this builder.
*/
@UnstableApi
public abstract class AbstractHttp2ConnectionHandlerBuilder<T extends Http2ConnectionHandler,
B extends AbstractHttp2ConnectionHandlerBuilder<T, B>> {
private static final SensitivityDetector DEFAULT_HEADER_SENSITIVITY_DETECTOR = Http2HeadersEncoder.NEVER_SENSITIVE;
// The properties that can always be set.
private Http2Settings initialSettings = Http2Settings.defaultSettings();
private Http2FrameListener frameListener;
private long gracefulShutdownTimeoutMillis = Http2CodecUtil.DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_MILLIS;
// The property that will prohibit connection() and codec() if set by server(),
// because this property is used only when this builder creates a Http2Connection.
private Boolean isServer;
private Integer maxReservedStreams;
// The property that will prohibit server() and codec() if set by connection().
private Http2Connection connection;
// The properties that will prohibit server() and connection() if set by codec().
private Http2ConnectionDecoder decoder;
private Http2ConnectionEncoder encoder;
// The properties that are:
// * mutually exclusive against codec() and
// * OK to use with server() and connection()
private Boolean validateHeaders;
private Http2FrameLogger frameLogger;
private SensitivityDetector headerSensitivityDetector;
private Boolean encoderEnforceMaxConcurrentStreams;
private Boolean encoderIgnoreMaxHeaderListSize;
private int initialHuffmanDecodeCapacity = DEFAULT_INITIAL_HUFFMAN_DECODE_CAPACITY;
Sets the Http2Settings
to use for the initial connection settings exchange. /**
* Sets the {@link Http2Settings} to use for the initial connection settings exchange.
*/
protected Http2Settings initialSettings() {
return initialSettings;
}
Sets the Http2Settings
to use for the initial connection settings exchange. /**
* Sets the {@link Http2Settings} to use for the initial connection settings exchange.
*/
protected B initialSettings(Http2Settings settings) {
initialSettings = checkNotNull(settings, "settings");
return self();
}
Returns the listener of inbound frames.
Returns: Http2FrameListener
if set, or null
if not set.
/**
* Returns the listener of inbound frames.
*
* @return {@link Http2FrameListener} if set, or {@code null} if not set.
*/
protected Http2FrameListener frameListener() {
return frameListener;
}
Sets the listener of inbound frames. This listener will only be set if the decoder's listener is null
. /**
* Sets the listener of inbound frames.
* This listener will only be set if the decoder's listener is {@code null}.
*/
protected B frameListener(Http2FrameListener frameListener) {
this.frameListener = checkNotNull(frameListener, "frameListener");
return self();
}
Returns the graceful shutdown timeout of the Http2Connection
in milliseconds. Returns -1 if the timeout is indefinite. /**
* Returns the graceful shutdown timeout of the {@link Http2Connection} in milliseconds. Returns -1 if the
* timeout is indefinite.
*/
protected long gracefulShutdownTimeoutMillis() {
return gracefulShutdownTimeoutMillis;
}
Sets the graceful shutdown timeout of the Http2Connection
in milliseconds. /**
* Sets the graceful shutdown timeout of the {@link Http2Connection} in milliseconds.
*/
protected B gracefulShutdownTimeoutMillis(long gracefulShutdownTimeoutMillis) {
if (gracefulShutdownTimeoutMillis < -1) {
throw new IllegalArgumentException("gracefulShutdownTimeoutMillis: " + gracefulShutdownTimeoutMillis +
" (expected: -1 for indefinite or >= 0)");
}
this.gracefulShutdownTimeoutMillis = gracefulShutdownTimeoutMillis;
return self();
}
/**
* Returns if {@link #build()} will to create a {@link Http2Connection} in server mode ({@code true})
* or client mode ({@code false}).
*/
protected boolean isServer() {
return isServer != null ? isServer : true;
}
/**
* Sets if {@link #build()} will to create a {@link Http2Connection} in server mode ({@code true})
* or client mode ({@code false}).
*/
protected B server(boolean isServer) {
enforceConstraint("server", "connection", connection);
enforceConstraint("server", "codec", decoder);
enforceConstraint("server", "codec", encoder);
this.isServer = isServer;
return self();
}
Get the maximum number of streams which can be in the reserved state at any given time.
By default this value will be ignored on the server for local endpoint. This is because the RFC provides
no way to explicitly communicate a limit to how many states can be in the reserved state, and instead relies
on the peer to send RST_STREAM frames when they will be rejected.
/**
* Get the maximum number of streams which can be in the reserved state at any given time.
* <p>
* By default this value will be ignored on the server for local endpoint. This is because the RFC provides
* no way to explicitly communicate a limit to how many states can be in the reserved state, and instead relies
* on the peer to send RST_STREAM frames when they will be rejected.
*/
protected int maxReservedStreams() {
return maxReservedStreams != null ? maxReservedStreams : DEFAULT_MAX_RESERVED_STREAMS;
}
Set the maximum number of streams which can be in the reserved state at any given time.
/**
* Set the maximum number of streams which can be in the reserved state at any given time.
*/
protected B maxReservedStreams(int maxReservedStreams) {
enforceConstraint("server", "connection", connection);
enforceConstraint("server", "codec", decoder);
enforceConstraint("server", "codec", encoder);
this.maxReservedStreams = checkPositiveOrZero(maxReservedStreams, "maxReservedStreams");
return self();
}
Returns the Http2Connection
to use. Returns: Http2Connection
if set, or null
if not set.
/**
* Returns the {@link Http2Connection} to use.
*
* @return {@link Http2Connection} if set, or {@code null} if not set.
*/
protected Http2Connection connection() {
return connection;
}
Sets the Http2Connection
to use. /**
* Sets the {@link Http2Connection} to use.
*/
protected B connection(Http2Connection connection) {
enforceConstraint("connection", "maxReservedStreams", maxReservedStreams);
enforceConstraint("connection", "server", isServer);
enforceConstraint("connection", "codec", decoder);
enforceConstraint("connection", "codec", encoder);
this.connection = checkNotNull(connection, "connection");
return self();
}
Returns the Http2ConnectionDecoder
to use. Returns: Http2ConnectionDecoder
if set, or null
if not set.
/**
* Returns the {@link Http2ConnectionDecoder} to use.
*
* @return {@link Http2ConnectionDecoder} if set, or {@code null} if not set.
*/
protected Http2ConnectionDecoder decoder() {
return decoder;
}
Returns the Http2ConnectionEncoder
to use. Returns: Http2ConnectionEncoder
if set, or null
if not set.
/**
* Returns the {@link Http2ConnectionEncoder} to use.
*
* @return {@link Http2ConnectionEncoder} if set, or {@code null} if not set.
*/
protected Http2ConnectionEncoder encoder() {
return encoder;
}
Sets the Http2ConnectionDecoder
and Http2ConnectionEncoder
to use. /**
* Sets the {@link Http2ConnectionDecoder} and {@link Http2ConnectionEncoder} to use.
*/
protected B codec(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder) {
enforceConstraint("codec", "server", isServer);
enforceConstraint("codec", "maxReservedStreams", maxReservedStreams);
enforceConstraint("codec", "connection", connection);
enforceConstraint("codec", "frameLogger", frameLogger);
enforceConstraint("codec", "validateHeaders", validateHeaders);
enforceConstraint("codec", "headerSensitivityDetector", headerSensitivityDetector);
enforceConstraint("codec", "encoderEnforceMaxConcurrentStreams", encoderEnforceMaxConcurrentStreams);
checkNotNull(decoder, "decoder");
checkNotNull(encoder, "encoder");
if (decoder.connection() != encoder.connection()) {
throw new IllegalArgumentException("The specified encoder and decoder have different connections.");
}
this.decoder = decoder;
this.encoder = encoder;
return self();
}
Returns if HTTP headers should be validated according to
RFC 7540, 8.1.2.6.
/**
* Returns if HTTP headers should be validated according to
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.6">RFC 7540, 8.1.2.6</a>.
*/
protected boolean isValidateHeaders() {
return validateHeaders != null ? validateHeaders : true;
}
Sets if HTTP headers should be validated according to
RFC 7540, 8.1.2.6.
/**
* Sets if HTTP headers should be validated according to
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.6">RFC 7540, 8.1.2.6</a>.
*/
protected B validateHeaders(boolean validateHeaders) {
enforceNonCodecConstraints("validateHeaders");
this.validateHeaders = validateHeaders;
return self();
}
Returns the logger that is used for the encoder and decoder.
Returns: Http2FrameLogger
if set, or null
if not set.
/**
* Returns the logger that is used for the encoder and decoder.
*
* @return {@link Http2FrameLogger} if set, or {@code null} if not set.
*/
protected Http2FrameLogger frameLogger() {
return frameLogger;
}
Sets the logger that is used for the encoder and decoder.
/**
* Sets the logger that is used for the encoder and decoder.
*/
protected B frameLogger(Http2FrameLogger frameLogger) {
enforceNonCodecConstraints("frameLogger");
this.frameLogger = checkNotNull(frameLogger, "frameLogger");
return self();
}
Returns if the encoder should queue frames if the maximum number of concurrent streams
would otherwise be exceeded.
/**
* Returns if the encoder should queue frames if the maximum number of concurrent streams
* would otherwise be exceeded.
*/
protected boolean encoderEnforceMaxConcurrentStreams() {
return encoderEnforceMaxConcurrentStreams != null ? encoderEnforceMaxConcurrentStreams : false;
}
Sets if the encoder should queue frames if the maximum number of concurrent streams
would otherwise be exceeded.
/**
* Sets if the encoder should queue frames if the maximum number of concurrent streams
* would otherwise be exceeded.
*/
protected B encoderEnforceMaxConcurrentStreams(boolean encoderEnforceMaxConcurrentStreams) {
enforceNonCodecConstraints("encoderEnforceMaxConcurrentStreams");
this.encoderEnforceMaxConcurrentStreams = encoderEnforceMaxConcurrentStreams;
return self();
}
Returns the SensitivityDetector
to use. /**
* Returns the {@link SensitivityDetector} to use.
*/
protected SensitivityDetector headerSensitivityDetector() {
return headerSensitivityDetector != null ? headerSensitivityDetector : DEFAULT_HEADER_SENSITIVITY_DETECTOR;
}
Sets the SensitivityDetector
to use. /**
* Sets the {@link SensitivityDetector} to use.
*/
protected B headerSensitivityDetector(SensitivityDetector headerSensitivityDetector) {
enforceNonCodecConstraints("headerSensitivityDetector");
this.headerSensitivityDetector = checkNotNull(headerSensitivityDetector, "headerSensitivityDetector");
return self();
}
Sets if the SETTINGS_MAX_HEADER_LIST_SIZE
should be ignored when encoding headers.
Params: - ignoreMaxHeaderListSize –
true
to ignore SETTINGS_MAX_HEADER_LIST_SIZE.
Returns: this.
/**
* Sets if the <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>
* should be ignored when encoding headers.
* @param ignoreMaxHeaderListSize {@code true} to ignore
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
* @return this.
*/
protected B encoderIgnoreMaxHeaderListSize(boolean ignoreMaxHeaderListSize) {
enforceNonCodecConstraints("encoderIgnoreMaxHeaderListSize");
this.encoderIgnoreMaxHeaderListSize = ignoreMaxHeaderListSize;
return self();
}
Sets the initial size of an intermediate buffer used during HPACK huffman decoding.
Params: - initialHuffmanDecodeCapacity – initial size of an intermediate buffer used during HPACK huffman decoding.
Returns: this.
/**
* Sets the initial size of an intermediate buffer used during HPACK huffman decoding.
* @param initialHuffmanDecodeCapacity initial size of an intermediate buffer used during HPACK huffman decoding.
* @return this.
*/
protected B initialHuffmanDecodeCapacity(int initialHuffmanDecodeCapacity) {
enforceNonCodecConstraints("initialHuffmanDecodeCapacity");
this.initialHuffmanDecodeCapacity = checkPositive(initialHuffmanDecodeCapacity, "initialHuffmanDecodeCapacity");
return self();
}
Create a new Http2ConnectionHandler
. /**
* Create a new {@link Http2ConnectionHandler}.
*/
protected T build() {
if (encoder != null) {
assert decoder != null;
return buildFromCodec(decoder, encoder);
}
Http2Connection connection = this.connection;
if (connection == null) {
connection = new DefaultHttp2Connection(isServer(), maxReservedStreams());
}
return buildFromConnection(connection);
}
private T buildFromConnection(Http2Connection connection) {
Long maxHeaderListSize = initialSettings.maxHeaderListSize();
Http2FrameReader reader = new DefaultHttp2FrameReader(new DefaultHttp2HeadersDecoder(isValidateHeaders(),
maxHeaderListSize == null ? DEFAULT_HEADER_LIST_SIZE : maxHeaderListSize,
initialHuffmanDecodeCapacity));
Http2FrameWriter writer = encoderIgnoreMaxHeaderListSize == null ?
new DefaultHttp2FrameWriter(headerSensitivityDetector()) :
new DefaultHttp2FrameWriter(headerSensitivityDetector(), encoderIgnoreMaxHeaderListSize);
if (frameLogger != null) {
reader = new Http2InboundFrameLogger(reader, frameLogger);
writer = new Http2OutboundFrameLogger(writer, frameLogger);
}
Http2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, writer);
boolean encoderEnforceMaxConcurrentStreams = encoderEnforceMaxConcurrentStreams();
if (encoderEnforceMaxConcurrentStreams) {
if (connection.isServer()) {
encoder.close();
reader.close();
throw new IllegalArgumentException(
"encoderEnforceMaxConcurrentStreams: " + encoderEnforceMaxConcurrentStreams +
" not supported for server");
}
encoder = new StreamBufferingEncoder(encoder);
}
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader);
return buildFromCodec(decoder, encoder);
}
private T buildFromCodec(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder) {
final T handler;
try {
// Call the abstract build method
handler = build(decoder, encoder, initialSettings);
} catch (Throwable t) {
encoder.close();
decoder.close();
throw new IllegalStateException("failed to build a Http2ConnectionHandler", t);
}
// Setup post build options
handler.gracefulShutdownTimeoutMillis(gracefulShutdownTimeoutMillis);
if (handler.decoder().frameListener() == null) {
handler.decoder().frameListener(frameListener);
}
return handler;
}
Implement this method to create a new Http2ConnectionHandler
or its subtype instance.
The return of this method will be subject to the following:
frameListener(Http2FrameListener)
will be set if not already set in the decoder
gracefulShutdownTimeoutMillis(long)
will always be set
/**
* Implement this method to create a new {@link Http2ConnectionHandler} or its subtype instance.
* <p>
* The return of this method will be subject to the following:
* <ul>
* <li>{@link #frameListener(Http2FrameListener)} will be set if not already set in the decoder</li>
* <li>{@link #gracefulShutdownTimeoutMillis(long)} will always be set</li>
* </ul>
*/
protected abstract T build(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
Http2Settings initialSettings) throws Exception;
Returns this
. /**
* Returns {@code this}.
*/
@SuppressWarnings("unchecked")
protected final B self() {
return (B) this;
}
private void enforceNonCodecConstraints(String rejected) {
enforceConstraint(rejected, "server/connection", decoder);
enforceConstraint(rejected, "server/connection", encoder);
}
private static void enforceConstraint(String methodName, String rejectorName, Object value) {
if (value != null) {
throw new IllegalStateException(
methodName + "() cannot be called because " + rejectorName + "() has been called already.");
}
}
}