/* * 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.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.handler.codec.http2.Http2PromisedRequestVerifier.ALWAYS_VERIFY; import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkPositive; import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; /** * Abstract base class which defines commonly used features required to build {@link Http2ConnectionHandler} instances. * *
* 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 {@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(); } /** * 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(); } /** * 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; } /** * 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 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)); 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); } DefaultHttp2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader, promisedRequestVerifier(), isAutoAckSettingsFrame()); 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 {@link Http2ConnectionHandler} or its subtype instance. *
* The return of this method will be subject to the following: *