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

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

There is a newer version: 5.0.0.Alpha2
Show newest version
/*
 * Copyright 2019 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:
 *
 *   https://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;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
import io.netty.channel.ServerChannel;
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
import io.netty.channel.socket.ChannelOutputShutdownEvent;
import io.netty.handler.codec.http2.Http2FrameCodec.DefaultHttp2FrameStream;
import io.netty.handler.ssl.SslCloseCompletionEvent;
import io.netty.util.ReferenceCounted;
import io.netty.util.internal.ObjectUtil;

import java.util.ArrayDeque;
import java.util.Queue;
import javax.net.ssl.SSLException;

import static io.netty.handler.codec.http2.AbstractHttp2StreamChannel.CHANNEL_INPUT_SHUTDOWN_READ_COMPLETE_VISITOR;
import static io.netty.handler.codec.http2.AbstractHttp2StreamChannel.CHANNEL_OUTPUT_SHUTDOWN_EVENT_VISITOR;
import static io.netty.handler.codec.http2.AbstractHttp2StreamChannel.SSL_CLOSE_COMPLETION_EVENT_VISITOR;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;

/**
 * An HTTP/2 handler that creates child channels for each stream. This handler must be used in combination
 * with {@link Http2FrameCodec}.
 *
 * 

When a new stream is created, a new {@link Http2StreamChannel} is created for it. Applications send and * receive {@link Http2StreamFrame}s on the created channel. {@link ByteBuf}s cannot be processed by the channel; * all writes that reach the head of the pipeline must be an instance of {@link Http2StreamFrame}. Writes that reach * the head of the pipeline are processed directly by this handler and cannot be intercepted. * *

The child channel will be notified of user events that impact the stream, such as {@link * Http2GoAwayFrame} and {@link Http2ResetFrame}, as soon as they occur. Although {@code * Http2GoAwayFrame} and {@code Http2ResetFrame} signify that the remote is ignoring further * communication, closing of the channel is delayed until any inbound queue is drained with {@link * Channel#read()}, which follows the default behavior of channels in Netty. Applications are * free to close the channel in response to such events if they don't have use for any queued * messages. Any connection level events like {@link Http2SettingsFrame} and {@link Http2GoAwayFrame} * will be processed internally and also propagated down the pipeline for other handlers to act on. * *

Outbound streams are supported via the {@link Http2StreamChannelBootstrap}. * *

{@link ChannelConfig#setMaxMessagesPerRead(int)} and {@link ChannelConfig#setAutoRead(boolean)} are supported. * *

Reference Counting

* * Some {@link Http2StreamFrame}s implement the {@link ReferenceCounted} interface, as they carry * reference counted objects (e.g. {@link ByteBuf}s). The multiplex codec will call {@link ReferenceCounted#retain()} * before propagating a reference counted object through the pipeline, and thus an application handler needs to release * such an object after having consumed it. For more information on reference counting take a look at * the reference counted docs. * *

Channel Events

* * A child channel becomes active as soon as it is registered to an {@link EventLoop}. Therefore, an active channel * does not map to an active HTTP/2 stream immediately. Only once a {@link Http2HeadersFrame} has been successfully sent * or received, does the channel map to an active HTTP/2 stream. In case it is not possible to open a new HTTP/2 stream * (i.e. due to the maximum number of active streams being exceeded), the child channel receives an exception * indicating the cause and is closed immediately thereafter. * *

Writability and Flow Control

* * A child channel observes outbound/remote flow control via the channel's writability. A channel only becomes writable * when it maps to an active HTTP/2 stream . A child channel does not know about the connection-level flow control * window. {@link ChannelHandler}s are free to ignore the channel's writability, in which case the excessive writes will * be buffered by the parent channel. It's important to note that only {@link Http2DataFrame}s are subject to * HTTP/2 flow control. * *

Closing a {@link Http2StreamChannel}

* * Once you close a {@link Http2StreamChannel} a {@link Http2ResetFrame} will be sent to the remote peer with * {@link Http2Error#CANCEL} if needed. If you want to close the stream with another {@link Http2Error} (due * errors / limits) you should propagate a {@link Http2FrameStreamException} through the {@link ChannelPipeline}. * Once it reaches the end of the {@link ChannelPipeline} it will automatically close the {@link Http2StreamChannel} * and send a {@link Http2ResetFrame} with the unwrapped {@link Http2Error} set. Another possibility is to just * directly write a {@link Http2ResetFrame} to the {@link Http2StreamChannel}l. */ public final class Http2MultiplexHandler extends Http2ChannelDuplexHandler { static final ChannelFutureListener CHILD_CHANNEL_REGISTRATION_LISTENER = new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { registerDone(future); } }; private final ChannelHandler inboundStreamHandler; private final ChannelHandler upgradeStreamHandler; private final Queue readCompletePendingQueue = new MaxCapacityQueue(new ArrayDeque(8), // Choose 100 which is what is used most of the times as default. Http2CodecUtil.SMALLEST_MAX_CONCURRENT_STREAMS); private boolean parentReadInProgress; private int idCount; // Need to be volatile as accessed from within the Http2MultiplexHandlerStreamChannel in a multi-threaded fashion. private volatile ChannelHandlerContext ctx; /** * Creates a new instance * * @param inboundStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of * the {@link Channel}s created for new inbound streams. */ public Http2MultiplexHandler(ChannelHandler inboundStreamHandler) { this(inboundStreamHandler, null); } /** * Creates a new instance * * @param inboundStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of * the {@link Channel}s created for new inbound streams. * @param upgradeStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of the * upgraded {@link Channel}. */ public Http2MultiplexHandler(ChannelHandler inboundStreamHandler, ChannelHandler upgradeStreamHandler) { this.inboundStreamHandler = ObjectUtil.checkNotNull(inboundStreamHandler, "inboundStreamHandler"); this.upgradeStreamHandler = upgradeStreamHandler; } static void registerDone(ChannelFuture future) { // Handle any errors that occurred on the local thread while registering. Even though // failures can happen after this point, they will be handled by the channel by closing the // childChannel. if (!future.isSuccess()) { Channel childChannel = future.channel(); if (childChannel.isRegistered()) { childChannel.close(); } else { childChannel.unsafe().closeForcibly(); } } } @Override protected void handlerAdded0(ChannelHandlerContext ctx) { if (ctx.executor() != ctx.channel().eventLoop()) { throw new IllegalStateException("EventExecutor must be EventLoop of Channel"); } this.ctx = ctx; } @Override protected void handlerRemoved0(ChannelHandlerContext ctx) { readCompletePendingQueue.clear(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { parentReadInProgress = true; if (msg instanceof Http2StreamFrame) { if (msg instanceof Http2WindowUpdateFrame) { // We dont want to propagate update frames to the user return; } Http2StreamFrame streamFrame = (Http2StreamFrame) msg; DefaultHttp2FrameStream s = (DefaultHttp2FrameStream) streamFrame.stream(); AbstractHttp2StreamChannel channel = (AbstractHttp2StreamChannel) s.attachment; if (msg instanceof Http2ResetFrame || msg instanceof Http2PriorityFrame) { // Reset and Priority frames needs to be propagated via user events as these are not flow-controlled and // so must not be controlled by suppressing channel.read() on the child channel. channel.pipeline().fireUserEventTriggered(msg); // RST frames will also trigger closing of the streams which then will call // AbstractHttp2StreamChannel.streamClosed() } else { channel.fireChildRead(streamFrame); } return; } if (msg instanceof Http2GoAwayFrame) { // goaway frames will also trigger closing of the streams which then will call // AbstractHttp2StreamChannel.streamClosed() onHttp2GoAwayFrame(ctx, (Http2GoAwayFrame) msg); } // Send everything down the pipeline ctx.fireChannelRead(msg); } @Override public void channelWritabilityChanged(final ChannelHandlerContext ctx) throws Exception { if (ctx.channel().isWritable()) { // While the writability state may change during iterating of the streams we just set all of the streams // to writable to not affect fairness. These will be "limited" by their own watermarks in any case. forEachActiveStream(AbstractHttp2StreamChannel.WRITABLE_VISITOR); } ctx.fireChannelWritabilityChanged(); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof Http2FrameStreamEvent) { Http2FrameStreamEvent event = (Http2FrameStreamEvent) evt; DefaultHttp2FrameStream stream = (DefaultHttp2FrameStream) event.stream(); if (event.type() == Http2FrameStreamEvent.Type.State) { switch (stream.state()) { case HALF_CLOSED_LOCAL: if (stream.id() != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) { // Ignore everything which was not caused by an upgrade break; } // fall-through case HALF_CLOSED_REMOTE: // fall-through case OPEN: if (stream.attachment != null) { // ignore if child channel was already created. break; } final AbstractHttp2StreamChannel ch; // We need to handle upgrades special when on the client side. if (stream.id() == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID && !isServer(ctx)) { // We must have an upgrade handler or else we can't handle the stream if (upgradeStreamHandler == null) { throw connectionError(INTERNAL_ERROR, "Client is misconfigured for upgrade requests"); } ch = new Http2MultiplexHandlerStreamChannel(stream, upgradeStreamHandler); ch.closeOutbound(); } else { ch = new Http2MultiplexHandlerStreamChannel(stream, inboundStreamHandler); } ChannelFuture future = ctx.channel().eventLoop().register(ch); if (future.isDone()) { registerDone(future); } else { future.addListener(CHILD_CHANNEL_REGISTRATION_LISTENER); } break; case CLOSED: AbstractHttp2StreamChannel channel = (AbstractHttp2StreamChannel) stream.attachment; if (channel != null) { channel.streamClosed(); } break; default: // ignore for now break; } } return; } if (evt == ChannelInputShutdownReadComplete.INSTANCE) { forEachActiveStream(CHANNEL_INPUT_SHUTDOWN_READ_COMPLETE_VISITOR); } else if (evt == ChannelOutputShutdownEvent.INSTANCE) { forEachActiveStream(CHANNEL_OUTPUT_SHUTDOWN_EVENT_VISITOR); } else if (evt == SslCloseCompletionEvent.SUCCESS) { forEachActiveStream(SSL_CLOSE_COMPLETION_EVENT_VISITOR); } ctx.fireUserEventTriggered(evt); } // TODO: This is most likely not the best way to expose this, need to think more about it. Http2StreamChannel newOutboundStream() { return new Http2MultiplexHandlerStreamChannel((DefaultHttp2FrameStream) newStream(), null); } @Override public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) throws Exception { if (cause instanceof Http2FrameStreamException) { Http2FrameStreamException exception = (Http2FrameStreamException) cause; Http2FrameStream stream = exception.stream(); AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel) ((DefaultHttp2FrameStream) stream).attachment; try { childChannel.pipeline().fireExceptionCaught(cause.getCause()); } finally { // Close with the correct error that causes this stream exception. // See https://github.com/netty/netty/issues/13235#issuecomment-1441994672 childChannel.closeWithError(exception.error()); } return; } if (cause instanceof Http2MultiplexActiveStreamsException) { // Unwrap the cause that was used to create it and fire it for all the active streams. fireExceptionCaughtForActiveStream(cause.getCause()); return; } if (cause.getCause() instanceof SSLException) { fireExceptionCaughtForActiveStream(cause); } ctx.fireExceptionCaught(cause); } private void fireExceptionCaughtForActiveStream(final Throwable cause) throws Http2Exception { forEachActiveStream(new Http2FrameStreamVisitor() { @Override public boolean visit(Http2FrameStream stream) { AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel) ((DefaultHttp2FrameStream) stream).attachment; childChannel.pipeline().fireExceptionCaught(cause); return true; } }); } private static boolean isServer(ChannelHandlerContext ctx) { return ctx.channel().parent() instanceof ServerChannel; } private void onHttp2GoAwayFrame(ChannelHandlerContext ctx, final Http2GoAwayFrame goAwayFrame) { if (goAwayFrame.lastStreamId() == Integer.MAX_VALUE) { // None of the streams can have an id greater than Integer.MAX_VALUE return; } // Notify which streams were not processed by the remote peer and are safe to retry on another connection: try { final boolean server = isServer(ctx); forEachActiveStream(new Http2FrameStreamVisitor() { @Override public boolean visit(Http2FrameStream stream) { final int streamId = stream.id(); if (streamId > goAwayFrame.lastStreamId() && Http2CodecUtil.isStreamIdValid(streamId, server)) { final AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel) ((DefaultHttp2FrameStream) stream).attachment; childChannel.pipeline().fireUserEventTriggered(goAwayFrame.retainedDuplicate()); } return true; } }); } catch (Http2Exception e) { ctx.fireExceptionCaught(e); ctx.close(); } } /** * Notifies any child streams of the read completion. */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { processPendingReadCompleteQueue(); ctx.fireChannelReadComplete(); } private void processPendingReadCompleteQueue() { parentReadInProgress = true; // If we have many child channel we can optimize for the case when multiple call flush() in // channelReadComplete(...) callbacks and only do it once as otherwise we will end-up with multiple // write calls on the socket which is expensive. AbstractHttp2StreamChannel childChannel = readCompletePendingQueue.poll(); if (childChannel != null) { try { do { childChannel.fireChildReadComplete(); childChannel = readCompletePendingQueue.poll(); } while (childChannel != null); } finally { parentReadInProgress = false; readCompletePendingQueue.clear(); ctx.flush(); } } else { parentReadInProgress = false; } } private final class Http2MultiplexHandlerStreamChannel extends AbstractHttp2StreamChannel { Http2MultiplexHandlerStreamChannel(DefaultHttp2FrameStream stream, ChannelHandler inboundHandler) { super(stream, ++idCount, inboundHandler); } @Override protected boolean isParentReadInProgress() { return parentReadInProgress; } @Override protected void addChannelToReadCompletePendingQueue() { // If there is no space left in the queue, just keep on processing everything that is already // stored there and try again. while (!readCompletePendingQueue.offer(this)) { processPendingReadCompleteQueue(); } } @Override protected ChannelHandlerContext parentContext() { return ctx; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy