All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.codec.http2.AbstractHttp2ConnectionHandlerBuilder Maven / Gradle / Ivy

There is a newer version: 5.0.0.Alpha2
Show newest version
/*
 * 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.channel.Channel;
import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
import io.netty.util.internal.ObjectUtil;
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_MAX_RESERVED_STREAMS;
import static io.netty.handler.codec.http2.Http2PromisedRequestVerifier.ALWAYS_VERIFY;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;

/**
 * Abstract base class which defines commonly used features required to build {@link Http2ConnectionHandler} instances.
 *
 * 

Three ways to build a {@link Http2ConnectionHandler}

*

Let the builder create a {@link Http2ConnectionHandler}

* 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: *
    *
  • {@link #connection(Http2Connection)}
  • *
  • {@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)}
  • *
* * *

Let the builder use the {@link Http2ConnectionHandler} you specified

* 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. *
    *
  • {@link #server(boolean)}
  • *
  • {@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)}
  • *
* *

Let the builder use the {@link Http2ConnectionDecoder} and {@link Http2ConnectionEncoder} you specified

* 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: *
    *
  • {@link #server(boolean)}
  • *
  • {@link #connection(Http2Connection)}
  • *
  • {@link #frameLogger(Http2FrameLogger)}
  • *
  • {@link #headerSensitivityDetector(SensitivityDetector)}
  • *
  • {@link #encoderEnforceMaxConcurrentStreams(boolean)}
  • *
  • {@link #encoderIgnoreMaxHeaderListSize(boolean)}
  • *
* *

Exposing necessary methods in a subclass

* {@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 The type of handler created by this builder. * @param The concrete type of this builder. */ @UnstableApi public abstract class AbstractHttp2ConnectionHandlerBuilder> { 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; private boolean decoupleCloseAndGoAway; // The property that will prohibit connection() and codec() if set by server(), // because this property is used only when this builder creates an 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 Http2PromisedRequestVerifier promisedRequestVerifier = ALWAYS_VERIFY; private boolean autoAckSettingsFrame = true; private boolean autoAckPingFrame = true; private int maxQueuedControlFrames = Http2CodecUtil.DEFAULT_MAX_QUEUED_CONTROL_FRAMES; private int maxConsecutiveEmptyFrames = 2; /** * Sets the {@link Http2Settings} to use for the initial connection settings exchange. */ protected Http2Settings initialSettings() { return initialSettings; } /** * 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. * * @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 {@code null}. */ protected B frameListener(Http2FrameListener frameListener) { this.frameListener = checkNotNull(frameListener, "frameListener"); return self(); } /** * 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 {@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. */ 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. */ 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 {@link Http2Connection} to use. * * @return {@link Http2Connection} if set, or {@code null} if not set. */ protected Http2Connection connection() { return connection; } /** * 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 {@link Http2ConnectionDecoder} to use. * * @return {@link Http2ConnectionDecoder} if set, or {@code null} if not set. */ protected Http2ConnectionDecoder decoder() { return decoder; } /** * Returns the {@link Http2ConnectionEncoder} to use. * * @return {@link Http2ConnectionEncoder} if set, or {@code null} if not set. */ protected Http2ConnectionEncoder encoder() { return encoder; } /** * 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. */ protected boolean isValidateHeaders() { return validateHeaders != null ? validateHeaders : true; } /** * Sets if HTTP headers should be validated according to * RFC 7540, 8.1.2.6. */ protected B validateHeaders(boolean validateHeaders) { enforceNonCodecConstraints("validateHeaders"); this.validateHeaders = validateHeaders; return self(); } /** * 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. */ 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. */ 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. */ protected B encoderEnforceMaxConcurrentStreams(boolean encoderEnforceMaxConcurrentStreams) { enforceNonCodecConstraints("encoderEnforceMaxConcurrentStreams"); this.encoderEnforceMaxConcurrentStreams = encoderEnforceMaxConcurrentStreams; return self(); } /** * Returns the maximum number of queued control frames that are allowed before the connection is closed. * This allows to protected against various attacks that can lead to high CPU / memory usage if the remote-peer * floods us with frames that would have us produce control frames, but stops to read from the underlying socket. * * {@code 0} means no protection is in place. */ protected int encoderEnforceMaxQueuedControlFrames() { return maxQueuedControlFrames; } /** * Sets the maximum number of queued control frames that are allowed before the connection is closed. * This allows to protected against various attacks that can lead to high CPU / memory usage if the remote-peer * floods us with frames that would have us produce control frames, but stops to read from the underlying socket. * * {@code 0} means no protection should be applied. */ protected B encoderEnforceMaxQueuedControlFrames(int maxQueuedControlFrames) { enforceNonCodecConstraints("encoderEnforceMaxQueuedControlFrames"); this.maxQueuedControlFrames = ObjectUtil.checkPositiveOrZero(maxQueuedControlFrames, "maxQueuedControlFrames"); return self(); } /** * Returns the {@link SensitivityDetector} to use. */ protected SensitivityDetector headerSensitivityDetector() { return headerSensitivityDetector != null ? headerSensitivityDetector : DEFAULT_HEADER_SENSITIVITY_DETECTOR; } /** * 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. * @param ignoreMaxHeaderListSize {@code true} to ignore * SETTINGS_MAX_HEADER_LIST_SIZE. * @return this. */ protected B encoderIgnoreMaxHeaderListSize(boolean ignoreMaxHeaderListSize) { enforceNonCodecConstraints("encoderIgnoreMaxHeaderListSize"); this.encoderIgnoreMaxHeaderListSize = ignoreMaxHeaderListSize; return self(); } /** * Does nothing, do not call. * * @deprecated Huffman decoding no longer depends on having a decode capacity. */ @Deprecated protected B initialHuffmanDecodeCapacity(int initialHuffmanDecodeCapacity) { return self(); } /** * Set the {@link Http2PromisedRequestVerifier} to use. * @return this. */ protected B promisedRequestVerifier(Http2PromisedRequestVerifier promisedRequestVerifier) { enforceNonCodecConstraints("promisedRequestVerifier"); this.promisedRequestVerifier = checkNotNull(promisedRequestVerifier, "promisedRequestVerifier"); return self(); } /** * Get the {@link Http2PromisedRequestVerifier} to use. * @return the {@link Http2PromisedRequestVerifier} to use. */ protected Http2PromisedRequestVerifier promisedRequestVerifier() { return promisedRequestVerifier; } /** * Returns the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed before * the connection is closed. This allows to protected against the remote peer flooding us with such frames and * so use up a lot of CPU. There is no valid use-case for empty DATA frames without end_of_stream flag. * * {@code 0} means no protection is in place. */ protected int decoderEnforceMaxConsecutiveEmptyDataFrames() { return maxConsecutiveEmptyFrames; } /** * Sets the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed before * the connection is closed. This allows to protected against the remote peer flooding us with such frames and * so use up a lot of CPU. There is no valid use-case for empty DATA frames without end_of_stream flag. * * {@code 0} means no protection should be applied. */ protected B decoderEnforceMaxConsecutiveEmptyDataFrames(int maxConsecutiveEmptyFrames) { enforceNonCodecConstraints("maxConsecutiveEmptyFrames"); this.maxConsecutiveEmptyFrames = ObjectUtil.checkPositiveOrZero( maxConsecutiveEmptyFrames, "maxConsecutiveEmptyFrames"); return self(); } /** * Determine if settings frame should automatically be acknowledged and applied. * @return this. */ protected B autoAckSettingsFrame(boolean autoAckSettings) { enforceNonCodecConstraints("autoAckSettingsFrame"); this.autoAckSettingsFrame = autoAckSettings; return self(); } /** * Determine if the SETTINGS frames should be automatically acknowledged and applied. * @return {@code true} if the SETTINGS frames should be automatically acknowledged and applied. */ protected boolean isAutoAckSettingsFrame() { return autoAckSettingsFrame; } /** * Determine if PING frame should automatically be acknowledged or not. * @return this. */ protected B autoAckPingFrame(boolean autoAckPingFrame) { enforceNonCodecConstraints("autoAckPingFrame"); this.autoAckPingFrame = autoAckPingFrame; return self(); } /** * Determine if the PING frames should be automatically acknowledged or not. * @return {@code true} if the PING frames should be automatically acknowledged. */ protected boolean isAutoAckPingFrame() { return autoAckPingFrame; } /** * Determine if the {@link Channel#close()} should be coupled with goaway and graceful close. * @param decoupleCloseAndGoAway {@code true} to make {@link Channel#close()} directly close the underlying * transport, and not attempt graceful closure via GOAWAY. * @return {@code this}. */ protected B decoupleCloseAndGoAway(boolean decoupleCloseAndGoAway) { this.decoupleCloseAndGoAway = decoupleCloseAndGoAway; return self(); } /** * Determine if the {@link Channel#close()} should be coupled with goaway and graceful close. */ protected boolean decoupleCloseAndGoAway() { return decoupleCloseAndGoAway; } /** * 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= */ -1)); 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 (maxQueuedControlFrames != 0) { encoder = new Http2ControlFrameLimitEncoder(encoder, maxQueuedControlFrames); } if (encoderEnforceMaxConcurrentStreams) { if (connection.isServer()) { encoder.close(); reader.close(); throw new IllegalArgumentException( "encoderEnforceMaxConcurrentStreams: " + encoderEnforceMaxConcurrentStreams + " not supported for server"); } encoder = new StreamBufferingEncoder(encoder); } DefaultHttp2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader, promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame()); return buildFromCodec(decoder, encoder); } private T buildFromCodec(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder) { int maxConsecutiveEmptyDataFrames = decoderEnforceMaxConsecutiveEmptyDataFrames(); if (maxConsecutiveEmptyDataFrames > 0) { decoder = new Http2EmptyDataFrameConnectionDecoder(decoder, maxConsecutiveEmptyDataFrames); } 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 an 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 {@link Http2ConnectionHandler} or its subtype instance. *

* The return of this method will be subject to the following: *

    *
  • {@link #frameListener(Http2FrameListener)} will be set if not already set in the decoder
  • *
  • {@link #gracefulShutdownTimeoutMillis(long)} will always be set
  • *
*/ protected abstract T build(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, Http2Settings initialSettings) throws Exception; /** * 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."); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy