1 /*
2 * Copyright 2019 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.http2;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.Channel;
20 import io.netty.channel.ChannelConfig;
21 import io.netty.channel.ChannelFuture;
22 import io.netty.channel.ChannelFutureListener;
23 import io.netty.channel.ChannelHandler;
24 import io.netty.channel.ChannelHandlerContext;
25 import io.netty.channel.ChannelPipeline;
26 import io.netty.channel.EventLoop;
27 import io.netty.channel.ServerChannel;
28 import io.netty.channel.socket.ChannelInputShutdownReadComplete;
29 import io.netty.channel.socket.ChannelOutputShutdownEvent;
30 import io.netty.handler.codec.http2.Http2FrameCodec.DefaultHttp2FrameStream;
31 import io.netty.handler.ssl.SslCloseCompletionEvent;
32 import io.netty.util.ReferenceCounted;
33 import io.netty.util.internal.ObjectUtil;
34
35 import java.util.ArrayDeque;
36 import java.util.ArrayList;
37 import java.util.HashSet;
38 import java.util.Queue;
39 import java.util.Set;
40 import javax.net.ssl.SSLException;
41
42 import static io.netty.handler.codec.http2.AbstractHttp2StreamChannel.CHANNEL_INPUT_SHUTDOWN_READ_COMPLETE_VISITOR;
43 import static io.netty.handler.codec.http2.AbstractHttp2StreamChannel.CHANNEL_OUTPUT_SHUTDOWN_EVENT_VISITOR;
44 import static io.netty.handler.codec.http2.AbstractHttp2StreamChannel.SSL_CLOSE_COMPLETION_EVENT_VISITOR;
45 import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
46 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
47
48 /**
49 * An HTTP/2 handler that creates child channels for each stream. This handler must be used in combination
50 * with {@link Http2FrameCodec}.
51 *
52 * <p>When a new stream is created, a new {@link Http2StreamChannel} is created for it. Applications send and
53 * receive {@link Http2StreamFrame}s on the created channel. {@link ByteBuf}s cannot be processed by the channel;
54 * all writes that reach the head of the pipeline must be an instance of {@link Http2StreamFrame}. Writes that reach
55 * the head of the pipeline are processed directly by this handler and cannot be intercepted.
56 *
57 * <p>The child channel will be notified of user events that impact the stream, such as {@link
58 * Http2GoAwayFrame} and {@link Http2ResetFrame}, as soon as they occur. Although {@code
59 * Http2GoAwayFrame} and {@code Http2ResetFrame} signify that the remote is ignoring further
60 * communication, closing of the channel is delayed until any inbound queue is drained with {@link
61 * Channel#read()}, which follows the default behavior of channels in Netty. Applications are
62 * free to close the channel in response to such events if they don't have use for any queued
63 * messages. Any connection level events like {@link Http2SettingsFrame} and {@link Http2GoAwayFrame}
64 * will be processed internally and also propagated down the pipeline for other handlers to act on.
65 *
66 * <p>Outbound streams are supported via the {@link Http2StreamChannelBootstrap}.
67 *
68 * <p>{@link ChannelConfig#setMaxMessagesPerRead(int)} and {@link ChannelConfig#setAutoRead(boolean)} are supported.
69 *
70 * <h3>Reference Counting</h3>
71 *
72 * Some {@link Http2StreamFrame}s implement the {@link ReferenceCounted} interface, as they carry
73 * reference counted objects (e.g. {@link ByteBuf}s). The multiplex codec will call {@link ReferenceCounted#retain()}
74 * before propagating a reference counted object through the pipeline, and thus an application handler needs to release
75 * such an object after having consumed it. For more information on reference counting take a look at
76 * <a href="https://netty.io/wiki/reference-counted-objects.html">the reference counted docs.</a>
77 *
78 * <h3>Channel Events</h3>
79 *
80 * A child channel becomes active as soon as it is registered to an {@link EventLoop}. Therefore, an active channel
81 * does not map to an active HTTP/2 stream immediately. Only once a {@link Http2HeadersFrame} has been successfully sent
82 * 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
83 * (i.e. due to the maximum number of active streams being exceeded), the child channel receives an exception
84 * indicating the cause and is closed immediately thereafter.
85 *
86 * <h3>Writability and Flow Control</h3>
87 *
88 * A child channel observes outbound/remote flow control via the channel's writability. A channel only becomes writable
89 * when it maps to an active HTTP/2 stream . A child channel does not know about the connection-level flow control
90 * window. {@link ChannelHandler}s are free to ignore the channel's writability, in which case the excessive writes will
91 * be buffered by the parent channel. It's important to note that only {@link Http2DataFrame}s are subject to
92 * HTTP/2 flow control.
93 *
94 * <h3>Closing a {@link Http2StreamChannel}</h3>
95 *
96 * Once you close a {@link Http2StreamChannel} a {@link Http2ResetFrame} will be sent to the remote peer with
97 * {@link Http2Error#CANCEL} if needed. If you want to close the stream with another {@link Http2Error} (due
98 * errors / limits) you should propagate a {@link Http2FrameStreamException} through the {@link ChannelPipeline}.
99 * Once it reaches the end of the {@link ChannelPipeline} it will automatically close the {@link Http2StreamChannel}
100 * and send a {@link Http2ResetFrame} with the unwrapped {@link Http2Error} set. Another possibility is to just
101 * directly write a {@link Http2ResetFrame} to the {@link Http2StreamChannel}l.
102 */
103 public final class Http2MultiplexHandler extends Http2ChannelDuplexHandler {
104
105 static final ChannelFutureListener CHILD_CHANNEL_REGISTRATION_LISTENER = Http2MultiplexHandler::registerDone;
106
107 private final ChannelHandler inboundStreamHandler;
108 private final ChannelHandler upgradeStreamHandler;
109 private final Queue<AbstractHttp2StreamChannel> readCompletePendingQueue =
110 new MaxCapacityQueue<AbstractHttp2StreamChannel>(new ArrayDeque<AbstractHttp2StreamChannel>(8),
111 // Choose 100 which is what is used most of the times as default.
112 Http2CodecUtil.SMALLEST_MAX_CONCURRENT_STREAMS);
113
114 // Outbound child channels opened via Http2StreamChannelBootstrap that were not closed yet. Until their first
115 // HEADERS frame is written their stream is unknown to the connection, so closing the connection doesn't close them.
116 private final Set<AbstractHttp2StreamChannel> outboundStreamChannels = new HashSet<AbstractHttp2StreamChannel>();
117 // Shared by all outbound child channels: removes a child channel from outboundStreamChannels once it is closed.
118 private final ChannelFutureListener outboundStreamChannelCloseListener =
119 future -> outboundStreamChannels.remove(future.channel());
120 private boolean parentReadInProgress;
121 private int idCount;
122
123 // Need to be volatile as accessed from within the Http2MultiplexHandlerStreamChannel in a multi-threaded fashion.
124 private volatile ChannelHandlerContext ctx;
125
126 /**
127 * Creates a new instance
128 *
129 * @param inboundStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of
130 * the {@link Channel}s created for new inbound streams.
131 */
132 public Http2MultiplexHandler(ChannelHandler inboundStreamHandler) {
133 this(inboundStreamHandler, null);
134 }
135
136 /**
137 * Creates a new instance
138 *
139 * @param inboundStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of
140 * the {@link Channel}s created for new inbound streams.
141 * @param upgradeStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of the
142 * upgraded {@link Channel}.
143 */
144 public Http2MultiplexHandler(ChannelHandler inboundStreamHandler, ChannelHandler upgradeStreamHandler) {
145 this.inboundStreamHandler = ObjectUtil.checkNotNull(inboundStreamHandler, "inboundStreamHandler");
146 this.upgradeStreamHandler = upgradeStreamHandler;
147 }
148
149 static void registerDone(ChannelFuture future) {
150 // Handle any errors that occurred on the local thread while registering. Even though
151 // failures can happen after this point, they will be handled by the channel by closing the
152 // childChannel.
153 if (!future.isSuccess()) {
154 Channel childChannel = future.channel();
155 if (childChannel.isRegistered()) {
156 childChannel.close();
157 } else {
158 childChannel.unsafe().closeForcibly();
159 }
160 }
161 }
162
163 @Override
164 protected void handlerAdded0(ChannelHandlerContext ctx) {
165 if (ctx.executor() != ctx.channel().eventLoop()) {
166 throw new IllegalStateException("EventExecutor must be EventLoop of Channel");
167 }
168 this.ctx = ctx;
169 }
170
171 @Override
172 protected void handlerRemoved0(ChannelHandlerContext ctx) {
173 readCompletePendingQueue.clear();
174 }
175
176 @Override
177 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
178 if (!outboundStreamChannels.isEmpty()) {
179 // The streams known to the connection were closed already. Close the outbound child channels whose
180 // stream was never created as well, so they see channelInactive() and their closeFuture() completes.
181 // Iterate over a copy, as closing a child channel removes it from outboundStreamChannels (see
182 // outboundStreamChannelCloseListener).
183 for (AbstractHttp2StreamChannel childChannel :
184 new ArrayList<AbstractHttp2StreamChannel>(outboundStreamChannels)) {
185 if (childChannel.stream().state() == Http2Stream.State.IDLE) {
186 childChannel.unsafe().closeForcibly();
187 }
188 }
189 }
190 ctx.fireChannelInactive();
191 }
192
193 @Override
194 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
195 parentReadInProgress = true;
196 if (msg instanceof Http2StreamFrame) {
197 if (msg instanceof Http2WindowUpdateFrame) {
198 // We dont want to propagate update frames to the user
199 return;
200 }
201 Http2StreamFrame streamFrame = (Http2StreamFrame) msg;
202 DefaultHttp2FrameStream s =
203 (DefaultHttp2FrameStream) streamFrame.stream();
204
205 AbstractHttp2StreamChannel channel = (AbstractHttp2StreamChannel) s.attachment;
206 if (msg instanceof Http2ResetFrame || msg instanceof Http2PriorityFrame) {
207 // Reset and Priority frames needs to be propagated via user events as these are not flow-controlled and
208 // so must not be controlled by suppressing channel.read() on the child channel.
209 channel.fireChildUserEventTriggered(msg);
210
211 // RST frames will also trigger closing of the streams which then will call
212 // AbstractHttp2StreamChannel.streamClosed()
213 } else {
214 channel.fireChildRead(streamFrame);
215 }
216 return;
217 }
218
219 if (msg instanceof Http2GoAwayFrame) {
220 // goaway frames will also trigger closing of the streams which then will call
221 // AbstractHttp2StreamChannel.streamClosed()
222 onHttp2GoAwayFrame(ctx, (Http2GoAwayFrame) msg);
223 }
224
225 // Send everything down the pipeline
226 ctx.fireChannelRead(msg);
227 }
228
229 @Override
230 public void channelWritabilityChanged(final ChannelHandlerContext ctx) throws Exception {
231 if (ctx.channel().isWritable()) {
232 // While the writability state may change during iterating of the streams we just set all of the streams
233 // to writable to not affect fairness. These will be "limited" by their own watermarks in any case.
234 forEachActiveStream(AbstractHttp2StreamChannel.WRITABLE_VISITOR);
235 }
236
237 ctx.fireChannelWritabilityChanged();
238 }
239
240 @Override
241 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
242 if (evt instanceof Http2FrameStreamEvent) {
243 Http2FrameStreamEvent event = (Http2FrameStreamEvent) evt;
244 DefaultHttp2FrameStream stream = (DefaultHttp2FrameStream) event.stream();
245 if (event.type() == Http2FrameStreamEvent.Type.State) {
246 switch (stream.state()) {
247 case HALF_CLOSED_LOCAL:
248 if (stream.id() != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
249 // Ignore everything which was not caused by an upgrade
250 break;
251 }
252 // fall-through
253 case HALF_CLOSED_REMOTE:
254 // fall-through
255 case OPEN:
256 if (stream.attachment != null) {
257 // ignore if child channel was already created.
258 break;
259 }
260 final AbstractHttp2StreamChannel ch;
261 // We need to handle upgrades special when on the client side.
262 if (stream.id() == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID && !isServer(ctx)) {
263 // We must have an upgrade handler or else we can't handle the stream
264 if (upgradeStreamHandler == null) {
265 throw connectionError(INTERNAL_ERROR,
266 "Client is misconfigured for upgrade requests");
267 }
268 ch = new Http2MultiplexHandlerStreamChannel(stream, upgradeStreamHandler);
269 ch.closeOutbound();
270 } else {
271 ch = new Http2MultiplexHandlerStreamChannel(stream, inboundStreamHandler);
272 }
273 ChannelFuture future = ctx.channel().eventLoop().register(ch);
274 if (future.isDone()) {
275 registerDone(future);
276 } else {
277 future.addListener(CHILD_CHANNEL_REGISTRATION_LISTENER);
278 }
279 break;
280 case CLOSED:
281 AbstractHttp2StreamChannel channel = (AbstractHttp2StreamChannel) stream.attachment;
282 if (channel != null) {
283 channel.streamClosed();
284 }
285 break;
286 default:
287 // ignore for now
288 break;
289 }
290 }
291 return;
292 }
293 if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
294 forEachActiveStream(CHANNEL_INPUT_SHUTDOWN_READ_COMPLETE_VISITOR);
295 } else if (evt == ChannelOutputShutdownEvent.INSTANCE) {
296 forEachActiveStream(CHANNEL_OUTPUT_SHUTDOWN_EVENT_VISITOR);
297 } else if (evt == SslCloseCompletionEvent.SUCCESS) {
298 forEachActiveStream(SSL_CLOSE_COMPLETION_EVENT_VISITOR);
299 }
300 ctx.fireUserEventTriggered(evt);
301 }
302
303 // TODO: This is most likely not the best way to expose this, need to think more about it.
304 Http2StreamChannel newOutboundStream() {
305 final Http2MultiplexHandlerStreamChannel childChannel =
306 new Http2MultiplexHandlerStreamChannel((DefaultHttp2FrameStream) newStream(), null);
307 outboundStreamChannels.add(childChannel);
308 childChannel.closeFuture().addListener(outboundStreamChannelCloseListener);
309 return childChannel;
310 }
311
312 @Override
313 public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) throws Exception {
314 if (cause instanceof Http2FrameStreamException) {
315 Http2FrameStreamException exception = (Http2FrameStreamException) cause;
316 Http2FrameStream stream = exception.stream();
317 AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
318 ((DefaultHttp2FrameStream) stream).attachment;
319 try {
320 childChannel.fireChildExceptionCaught(cause.getCause());
321 } finally {
322 // Close with the correct error that causes this stream exception.
323 // See https://github.com/netty/netty/issues/13235#issuecomment-1441994672
324 childChannel.closeWithError(exception.error());
325 }
326 return;
327 }
328 if (cause instanceof Http2MultiplexActiveStreamsException) {
329 // Unwrap the cause that was used to create it and fire it for all the active streams.
330 fireExceptionCaughtForActiveStream(cause.getCause());
331 return;
332 }
333
334 if (cause.getCause() instanceof SSLException) {
335 fireExceptionCaughtForActiveStream(cause);
336 }
337 ctx.fireExceptionCaught(cause);
338 }
339
340 private void fireExceptionCaughtForActiveStream(final Throwable cause) throws Http2Exception {
341 forEachActiveStream(new Http2FrameStreamVisitor() {
342 @Override
343 public boolean visit(Http2FrameStream stream) {
344 AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
345 ((DefaultHttp2FrameStream) stream).attachment;
346 childChannel.fireChildExceptionCaught(cause);
347 return true;
348 }
349 });
350 }
351
352 private static boolean isServer(ChannelHandlerContext ctx) {
353 return ctx.channel().parent() instanceof ServerChannel;
354 }
355
356 private void onHttp2GoAwayFrame(ChannelHandlerContext ctx, final Http2GoAwayFrame goAwayFrame) {
357 if (goAwayFrame.lastStreamId() == Integer.MAX_VALUE) {
358 // None of the streams can have an id greater than Integer.MAX_VALUE
359 return;
360 }
361 // Notify which streams were not processed by the remote peer and are safe to retry on another connection:
362 try {
363 final boolean server = isServer(ctx);
364 forEachActiveStream(new Http2FrameStreamVisitor() {
365 @Override
366 public boolean visit(Http2FrameStream stream) {
367 final int streamId = stream.id();
368 if (streamId > goAwayFrame.lastStreamId() && Http2CodecUtil.isStreamIdValid(streamId, server)) {
369 final AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
370 ((DefaultHttp2FrameStream) stream).attachment;
371 childChannel.fireChildUserEventTriggered(goAwayFrame.retainedDuplicate());
372 }
373 return true;
374 }
375 });
376 } catch (Http2Exception e) {
377 ctx.fireExceptionCaught(e);
378 ctx.close();
379 }
380 }
381
382 /**
383 * Notifies any child streams of the read completion.
384 */
385 @Override
386 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
387 processPendingReadCompleteQueue();
388 ctx.fireChannelReadComplete();
389 }
390
391 private void processPendingReadCompleteQueue() {
392 parentReadInProgress = true;
393 // If we have many child channel we can optimize for the case when multiple call flush() in
394 // channelReadComplete(...) callbacks and only do it once as otherwise we will end-up with multiple
395 // write calls on the socket which is expensive.
396 AbstractHttp2StreamChannel childChannel = readCompletePendingQueue.poll();
397 if (childChannel != null) {
398 try {
399 do {
400 childChannel.fireChildReadComplete();
401 childChannel = readCompletePendingQueue.poll();
402 } while (childChannel != null);
403 } finally {
404 parentReadInProgress = false;
405 readCompletePendingQueue.clear();
406 ctx.flush();
407 }
408 } else {
409 parentReadInProgress = false;
410 }
411 }
412
413 private final class Http2MultiplexHandlerStreamChannel extends AbstractHttp2StreamChannel {
414
415 Http2MultiplexHandlerStreamChannel(DefaultHttp2FrameStream stream, ChannelHandler inboundHandler) {
416 super(stream, ++idCount, inboundHandler);
417 }
418
419 @Override
420 protected boolean isParentReadInProgress() {
421 return parentReadInProgress;
422 }
423
424 @Override
425 protected void addChannelToReadCompletePendingQueue() {
426 // If there is no space left in the queue, just keep on processing everything that is already
427 // stored there and try again.
428 while (!readCompletePendingQueue.offer(this)) {
429 processPendingReadCompleteQueue();
430 }
431 }
432
433 @Override
434 protected ChannelHandlerContext parentContext() {
435 return ctx;
436 }
437 }
438 }