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 = new ChannelFutureListener() {
106 @Override
107 public void operationComplete(ChannelFuture future) {
108 registerDone(future);
109 }
110 };
111
112 private final ChannelHandler inboundStreamHandler;
113 private final ChannelHandler upgradeStreamHandler;
114 private final Queue<AbstractHttp2StreamChannel> readCompletePendingQueue =
115 new MaxCapacityQueue<AbstractHttp2StreamChannel>(new ArrayDeque<AbstractHttp2StreamChannel>(8),
116 // Choose 100 which is what is used most of the times as default.
117 Http2CodecUtil.SMALLEST_MAX_CONCURRENT_STREAMS);
118
119 // Outbound child channels opened via Http2StreamChannelBootstrap that were not closed yet. Until their first
120 // HEADERS frame is written their stream is unknown to the connection, so closing the connection doesn't close them.
121 private final Set<AbstractHttp2StreamChannel> outboundStreamChannels = new HashSet<AbstractHttp2StreamChannel>();
122 // Shared by all outbound child channels: removes a child channel from outboundStreamChannels once it is closed.
123 private final ChannelFutureListener outboundStreamChannelCloseListener = new ChannelFutureListener() {
124 @Override
125 public void operationComplete(ChannelFuture future) {
126 outboundStreamChannels.remove(future.channel());
127 }
128 };
129 private boolean parentReadInProgress;
130 private int idCount;
131
132 // Need to be volatile as accessed from within the Http2MultiplexHandlerStreamChannel in a multi-threaded fashion.
133 private volatile ChannelHandlerContext ctx;
134
135 /**
136 * Creates a new instance
137 *
138 * @param inboundStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of
139 * the {@link Channel}s created for new inbound streams.
140 */
141 public Http2MultiplexHandler(ChannelHandler inboundStreamHandler) {
142 this(inboundStreamHandler, null);
143 }
144
145 /**
146 * Creates a new instance
147 *
148 * @param inboundStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of
149 * the {@link Channel}s created for new inbound streams.
150 * @param upgradeStreamHandler the {@link ChannelHandler} that will be added to the {@link ChannelPipeline} of the
151 * upgraded {@link Channel}.
152 */
153 public Http2MultiplexHandler(ChannelHandler inboundStreamHandler, ChannelHandler upgradeStreamHandler) {
154 this.inboundStreamHandler = ObjectUtil.checkNotNull(inboundStreamHandler, "inboundStreamHandler");
155 this.upgradeStreamHandler = upgradeStreamHandler;
156 }
157
158 static void registerDone(ChannelFuture future) {
159 // Handle any errors that occurred on the local thread while registering. Even though
160 // failures can happen after this point, they will be handled by the channel by closing the
161 // childChannel.
162 if (!future.isSuccess()) {
163 Channel childChannel = future.channel();
164 if (childChannel.isRegistered()) {
165 childChannel.close();
166 } else {
167 childChannel.unsafe().closeForcibly();
168 }
169 }
170 }
171
172 @Override
173 protected void handlerAdded0(ChannelHandlerContext ctx) {
174 if (ctx.executor() != ctx.channel().eventLoop()) {
175 throw new IllegalStateException("EventExecutor must be EventLoop of Channel");
176 }
177 this.ctx = ctx;
178 }
179
180 @Override
181 protected void handlerRemoved0(ChannelHandlerContext ctx) {
182 readCompletePendingQueue.clear();
183 }
184
185 @Override
186 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
187 if (!outboundStreamChannels.isEmpty()) {
188 // The streams known to the connection were closed already. Close the outbound child channels whose
189 // stream was never created as well, so they see channelInactive() and their closeFuture() completes.
190 // Iterate over a copy, as closing a child channel removes it from outboundStreamChannels (see
191 // outboundStreamChannelCloseListener).
192 for (AbstractHttp2StreamChannel childChannel :
193 new ArrayList<AbstractHttp2StreamChannel>(outboundStreamChannels)) {
194 if (childChannel.stream().state() == Http2Stream.State.IDLE) {
195 childChannel.unsafe().closeForcibly();
196 }
197 }
198 }
199 ctx.fireChannelInactive();
200 }
201
202 @Override
203 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
204 parentReadInProgress = true;
205 if (msg instanceof Http2StreamFrame) {
206 if (msg instanceof Http2WindowUpdateFrame) {
207 // We dont want to propagate update frames to the user
208 return;
209 }
210 Http2StreamFrame streamFrame = (Http2StreamFrame) msg;
211 DefaultHttp2FrameStream s =
212 (DefaultHttp2FrameStream) streamFrame.stream();
213
214 AbstractHttp2StreamChannel channel = (AbstractHttp2StreamChannel) s.attachment;
215 if (msg instanceof Http2ResetFrame || msg instanceof Http2PriorityFrame) {
216 // Reset and Priority frames needs to be propagated via user events as these are not flow-controlled and
217 // so must not be controlled by suppressing channel.read() on the child channel.
218 channel.fireChildUserEventTriggered(msg);
219
220 // RST frames will also trigger closing of the streams which then will call
221 // AbstractHttp2StreamChannel.streamClosed()
222 } else {
223 channel.fireChildRead(streamFrame);
224 }
225 return;
226 }
227
228 if (msg instanceof Http2GoAwayFrame) {
229 // goaway frames will also trigger closing of the streams which then will call
230 // AbstractHttp2StreamChannel.streamClosed()
231 onHttp2GoAwayFrame(ctx, (Http2GoAwayFrame) msg);
232 }
233
234 // Send everything down the pipeline
235 ctx.fireChannelRead(msg);
236 }
237
238 @Override
239 public void channelWritabilityChanged(final ChannelHandlerContext ctx) throws Exception {
240 if (ctx.channel().isWritable()) {
241 // While the writability state may change during iterating of the streams we just set all of the streams
242 // to writable to not affect fairness. These will be "limited" by their own watermarks in any case.
243 forEachActiveStream(AbstractHttp2StreamChannel.WRITABLE_VISITOR);
244 }
245
246 ctx.fireChannelWritabilityChanged();
247 }
248
249 @Override
250 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
251 if (evt instanceof Http2FrameStreamEvent) {
252 Http2FrameStreamEvent event = (Http2FrameStreamEvent) evt;
253 DefaultHttp2FrameStream stream = (DefaultHttp2FrameStream) event.stream();
254 if (event.type() == Http2FrameStreamEvent.Type.State) {
255 switch (stream.state()) {
256 case HALF_CLOSED_LOCAL:
257 if (stream.id() != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
258 // Ignore everything which was not caused by an upgrade
259 break;
260 }
261 // fall-through
262 case HALF_CLOSED_REMOTE:
263 // fall-through
264 case OPEN:
265 if (stream.attachment != null) {
266 // ignore if child channel was already created.
267 break;
268 }
269 final AbstractHttp2StreamChannel ch;
270 // We need to handle upgrades special when on the client side.
271 if (stream.id() == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID && !isServer(ctx)) {
272 // We must have an upgrade handler or else we can't handle the stream
273 if (upgradeStreamHandler == null) {
274 throw connectionError(INTERNAL_ERROR,
275 "Client is misconfigured for upgrade requests");
276 }
277 ch = new Http2MultiplexHandlerStreamChannel(stream, upgradeStreamHandler);
278 ch.closeOutbound();
279 } else {
280 ch = new Http2MultiplexHandlerStreamChannel(stream, inboundStreamHandler);
281 }
282 ChannelFuture future = ctx.channel().eventLoop().register(ch);
283 if (future.isDone()) {
284 registerDone(future);
285 } else {
286 future.addListener(CHILD_CHANNEL_REGISTRATION_LISTENER);
287 }
288 break;
289 case CLOSED:
290 AbstractHttp2StreamChannel channel = (AbstractHttp2StreamChannel) stream.attachment;
291 if (channel != null) {
292 channel.streamClosed();
293 }
294 break;
295 default:
296 // ignore for now
297 break;
298 }
299 }
300 return;
301 }
302 if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
303 forEachActiveStream(CHANNEL_INPUT_SHUTDOWN_READ_COMPLETE_VISITOR);
304 } else if (evt == ChannelOutputShutdownEvent.INSTANCE) {
305 forEachActiveStream(CHANNEL_OUTPUT_SHUTDOWN_EVENT_VISITOR);
306 } else if (evt == SslCloseCompletionEvent.SUCCESS) {
307 forEachActiveStream(SSL_CLOSE_COMPLETION_EVENT_VISITOR);
308 }
309 ctx.fireUserEventTriggered(evt);
310 }
311
312 // TODO: This is most likely not the best way to expose this, need to think more about it.
313 Http2StreamChannel newOutboundStream() {
314 final Http2MultiplexHandlerStreamChannel childChannel =
315 new Http2MultiplexHandlerStreamChannel((DefaultHttp2FrameStream) newStream(), null);
316 outboundStreamChannels.add(childChannel);
317 childChannel.closeFuture().addListener(outboundStreamChannelCloseListener);
318 return childChannel;
319 }
320
321 @Override
322 public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) throws Exception {
323 if (cause instanceof Http2FrameStreamException) {
324 Http2FrameStreamException exception = (Http2FrameStreamException) cause;
325 Http2FrameStream stream = exception.stream();
326 AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
327 ((DefaultHttp2FrameStream) stream).attachment;
328 try {
329 childChannel.fireChildExceptionCaught(cause.getCause());
330 } finally {
331 // Close with the correct error that causes this stream exception.
332 // See https://github.com/netty/netty/issues/13235#issuecomment-1441994672
333 childChannel.closeWithError(exception.error());
334 }
335 return;
336 }
337 if (cause instanceof Http2MultiplexActiveStreamsException) {
338 // Unwrap the cause that was used to create it and fire it for all the active streams.
339 fireExceptionCaughtForActiveStream(cause.getCause());
340 return;
341 }
342
343 if (cause.getCause() instanceof SSLException) {
344 fireExceptionCaughtForActiveStream(cause);
345 }
346 ctx.fireExceptionCaught(cause);
347 }
348
349 private void fireExceptionCaughtForActiveStream(final Throwable cause) throws Http2Exception {
350 forEachActiveStream(new Http2FrameStreamVisitor() {
351 @Override
352 public boolean visit(Http2FrameStream stream) {
353 AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
354 ((DefaultHttp2FrameStream) stream).attachment;
355 childChannel.fireChildExceptionCaught(cause);
356 return true;
357 }
358 });
359 }
360
361 private static boolean isServer(ChannelHandlerContext ctx) {
362 return ctx.channel().parent() instanceof ServerChannel;
363 }
364
365 private void onHttp2GoAwayFrame(ChannelHandlerContext ctx, final Http2GoAwayFrame goAwayFrame) {
366 if (goAwayFrame.lastStreamId() == Integer.MAX_VALUE) {
367 // None of the streams can have an id greater than Integer.MAX_VALUE
368 return;
369 }
370 // Notify which streams were not processed by the remote peer and are safe to retry on another connection:
371 try {
372 final boolean server = isServer(ctx);
373 forEachActiveStream(new Http2FrameStreamVisitor() {
374 @Override
375 public boolean visit(Http2FrameStream stream) {
376 final int streamId = stream.id();
377 if (streamId > goAwayFrame.lastStreamId() && Http2CodecUtil.isStreamIdValid(streamId, server)) {
378 final AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
379 ((DefaultHttp2FrameStream) stream).attachment;
380 childChannel.fireChildUserEventTriggered(goAwayFrame.retainedDuplicate());
381 }
382 return true;
383 }
384 });
385 } catch (Http2Exception e) {
386 ctx.fireExceptionCaught(e);
387 ctx.close();
388 }
389 }
390
391 /**
392 * Notifies any child streams of the read completion.
393 */
394 @Override
395 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
396 processPendingReadCompleteQueue();
397 ctx.fireChannelReadComplete();
398 }
399
400 private void processPendingReadCompleteQueue() {
401 parentReadInProgress = true;
402 // If we have many child channel we can optimize for the case when multiple call flush() in
403 // channelReadComplete(...) callbacks and only do it once as otherwise we will end-up with multiple
404 // write calls on the socket which is expensive.
405 AbstractHttp2StreamChannel childChannel = readCompletePendingQueue.poll();
406 if (childChannel != null) {
407 try {
408 do {
409 childChannel.fireChildReadComplete();
410 childChannel = readCompletePendingQueue.poll();
411 } while (childChannel != null);
412 } finally {
413 parentReadInProgress = false;
414 readCompletePendingQueue.clear();
415 ctx.flush();
416 }
417 } else {
418 parentReadInProgress = false;
419 }
420 }
421
422 private final class Http2MultiplexHandlerStreamChannel extends AbstractHttp2StreamChannel {
423
424 Http2MultiplexHandlerStreamChannel(DefaultHttp2FrameStream stream, ChannelHandler inboundHandler) {
425 super(stream, ++idCount, inboundHandler);
426 }
427
428 @Override
429 protected boolean isParentReadInProgress() {
430 return parentReadInProgress;
431 }
432
433 @Override
434 protected void addChannelToReadCompletePendingQueue() {
435 // If there is no space left in the queue, just keep on processing everything that is already
436 // stored there and try again.
437 while (!readCompletePendingQueue.offer(this)) {
438 processPendingReadCompleteQueue();
439 }
440 }
441
442 @Override
443 protected ChannelHandlerContext parentContext() {
444 return ctx;
445 }
446 }
447 }