View Javadoc
1   /*
2    * Copyright 2020 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.http3;
17  
18  import io.netty.channel.ChannelHandler;
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.channel.ChannelInboundHandlerAdapter;
21  import io.netty.handler.codec.http3.Http3FrameCodec.Http3FrameCodecFactory;
22  import io.netty.handler.codec.quic.QuicChannel;
23  import io.netty.handler.codec.quic.QuicStreamChannel;
24  import io.netty.handler.codec.quic.QuicStreamType;
25  import org.jetbrains.annotations.Nullable;
26  
27  import java.util.function.LongFunction;
28  
29  import static io.netty.handler.codec.http3.Http3RequestStreamCodecState.NO_STATE;
30  import static io.netty.handler.codec.http3.Http3SettingsFrame.HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS;
31  import static io.netty.handler.codec.http3.Http3SettingsFrame.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY;
32  import static java.lang.Math.toIntExact;
33  
34  /**
35   * Handler that handles <a href="https://tools.ietf.org/html/draft-ietf-quic-http-32">HTTP3</a> connections.
36   */
37  public abstract class Http3ConnectionHandler extends ChannelInboundHandlerAdapter {
38      final Http3FrameCodecFactory codecFactory;
39      final LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory;
40      final boolean disableQpackDynamicTable;
41      final Http3ControlStreamInboundHandler localControlStreamHandler;
42      final Http3ControlStreamOutboundHandler remoteControlStreamHandler;
43      final QpackDecoder qpackDecoder;
44      final QpackEncoder qpackEncoder;
45      private boolean controlStreamCreationInProgress;
46  
47      final long maxTableCapacity;
48  
49      /**
50       * Create a new instance.
51       * @param server                                {@code true} if server-side, {@code false} otherwise.
52       * @param inboundControlStreamHandler           the {@link ChannelHandler} which will be notified about
53       *                                              {@link Http3RequestStreamFrame}s or {@code null} if the user is not
54       *                                              interested in these.
55       * @param unknownInboundStreamHandlerFactory    the {@link LongFunction} that will provide a custom
56       *                                              {@link ChannelHandler} for unknown inbound stream types or
57       *                                              {@code null} if no special handling should be done.
58       * @param localSettings                         the local {@link Http3SettingsFrame} that should be sent to the
59       *                                              remote peer or {@code null} if the default settings should be used.
60       * @param disableQpackDynamicTable              If QPACK dynamic table should be disabled.
61       */
62      Http3ConnectionHandler(boolean server, @Nullable ChannelHandler inboundControlStreamHandler,
63                             @Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
64                             @Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) {
65          this.unknownInboundStreamHandlerFactory = unknownInboundStreamHandlerFactory;
66          this.disableQpackDynamicTable = disableQpackDynamicTable;
67          if (localSettings == null) {
68              localSettings = new DefaultHttp3SettingsFrame();
69          } else {
70              localSettings = DefaultHttp3SettingsFrame.copyOf(localSettings);
71          }
72          Long maxFieldSectionSize = localSettings.get(Http3SettingsFrame.HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE);
73          if (maxFieldSectionSize == null) {
74               // Default value in rfc is unlimited
75               // but Quic can have max 2^62-1 max value as TWO bits reserved for Variable-Length Integer Encoding
76              maxFieldSectionSize = (1L << 62) - 1;
77          }
78          this.maxTableCapacity = localSettings.getOrDefault(HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0);
79          int maxBlockedStreams = toIntExact(localSettings.getOrDefault(HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS, 0));
80          qpackDecoder = new QpackDecoder(maxTableCapacity, maxBlockedStreams);
81          qpackEncoder = new QpackEncoder();
82          codecFactory = Http3FrameCodec.newFactory(qpackDecoder, maxFieldSectionSize, qpackEncoder);
83          remoteControlStreamHandler =  new Http3ControlStreamOutboundHandler(server, localSettings,
84                  codecFactory.newCodec(Http3FrameTypeValidator.NO_VALIDATION, NO_STATE, NO_STATE));
85          localControlStreamHandler = new Http3ControlStreamInboundHandler(server, inboundControlStreamHandler,
86                  qpackEncoder, remoteControlStreamHandler);
87      }
88  
89      private void createControlStreamIfNeeded(ChannelHandlerContext ctx) {
90          if (!controlStreamCreationInProgress && Http3.getLocalControlStream(ctx.channel()) == null) {
91              controlStreamCreationInProgress = true;
92              QuicChannel channel = (QuicChannel) ctx.channel();
93              // Once the channel became active we need to create an unidirectional stream and write the
94              // Http3SettingsFrame to it. This needs to be the first frame on this stream.
95              // https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.2.1.
96              channel.createStream(QuicStreamType.UNIDIRECTIONAL, remoteControlStreamHandler)
97                      .addListener(f -> {
98                          if (!f.isSuccess()) {
99                              ctx.fireExceptionCaught(new Http3Exception(Http3ErrorCode.H3_STREAM_CREATION_ERROR,
100                                     "Unable to open control stream", f.cause()));
101                             ctx.close();
102                         } else {
103                             Http3.setLocalControlStream(channel, (QuicStreamChannel) f.getNow());
104                         }
105                     });
106         }
107     }
108 
109     /**
110      * Returns {@code true} if we received a GOAWAY frame from the remote peer.
111      * @return {@code true} if we received the frame, {@code false} otherwise.
112      */
113     public final boolean isGoAwayReceived() {
114         return localControlStreamHandler.isGoAwayReceived();
115     }
116 
117     /**
118      * Returns a new codec that will encode and decode {@link Http3Frame}s for this HTTP/3 connection.
119      *
120      * @return a new codec.
121      */
122     final ChannelHandler newCodec(Http3RequestStreamCodecState encodeState,
123                                   Http3RequestStreamCodecState decodeState) {
124         return codecFactory.newCodec(Http3RequestStreamFrameTypeValidator.INSTANCE, encodeState, decodeState);
125     }
126 
127     final ChannelHandler newRequestStreamValidationHandler(
128             QuicStreamChannel forStream, Http3RequestStreamCodecState encodeState,
129             Http3RequestStreamCodecState decodeState) {
130         final QpackAttributes qpackAttributes = Http3.getQpackAttributes(forStream.parent());
131         assert qpackAttributes != null;
132         if (localControlStreamHandler.isServer()) {
133             return Http3RequestStreamValidationHandler.newServerValidator(qpackAttributes, qpackDecoder,
134                     encodeState, decodeState);
135         }
136         return Http3RequestStreamValidationHandler.newClientValidator(localControlStreamHandler::isGoAwayReceived,
137                 qpackAttributes, qpackDecoder, encodeState, decodeState);
138     }
139 
140     final ChannelHandler newPushStreamValidationHandler(QuicStreamChannel forStream,
141                                                         Http3RequestStreamCodecState decodeState) {
142         if (localControlStreamHandler.isServer()) {
143             return Http3PushStreamServerValidationHandler.INSTANCE;
144         }
145         final QpackAttributes qpackAttributes = Http3.getQpackAttributes(forStream.parent());
146         assert qpackAttributes != null;
147         return new Http3PushStreamClientValidationHandler(qpackAttributes, qpackDecoder, decodeState);
148     }
149 
150     @Override
151     public void handlerAdded(ChannelHandlerContext ctx) {
152         QuicChannel channel = (QuicChannel) ctx.channel();
153         Http3.setQpackAttributes(channel, new QpackAttributes(channel, disableQpackDynamicTable));
154         if (ctx.channel().isActive()) {
155             createControlStreamIfNeeded(ctx);
156         }
157     }
158 
159     @Override
160     public void channelActive(ChannelHandlerContext ctx) {
161         createControlStreamIfNeeded(ctx);
162 
163         ctx.fireChannelActive();
164     }
165 
166     @Override
167     public void channelRead(ChannelHandlerContext ctx, Object msg) {
168         if (msg instanceof QuicStreamChannel) {
169             QuicStreamChannel channel = (QuicStreamChannel) msg;
170             switch (channel.type()) {
171                 case BIDIRECTIONAL:
172                     initBidirectionalStream(ctx, channel);
173                     break;
174                 case UNIDIRECTIONAL:
175                     initUnidirectionalStream(ctx, channel);
176                     break;
177                 default:
178                     throw new Error("Unexpected channel type: " + channel.type());
179             }
180         }
181         ctx.fireChannelRead(msg);
182     }
183 
184     /**
185      * Called when an bidirectional stream is opened from the remote-peer.
186      *
187      * @param ctx           the {@link ChannelHandlerContext} of the parent {@link QuicChannel}.
188      * @param streamChannel the {@link QuicStreamChannel}.
189      */
190     abstract void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel);
191 
192     /**
193      * Called when an unidirectional stream is opened from the remote-peer.
194      *
195      * @param ctx           the {@link ChannelHandlerContext} of the parent {@link QuicChannel}.
196      * @param streamChannel the {@link QuicStreamChannel}.
197      */
198     abstract void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel);
199 
200     long maxTableCapacity() {
201         return maxTableCapacity;
202     }
203 
204     /**
205      * Always returns {@code false} as it keeps state.
206      */
207     @Override
208     public boolean isSharable() {
209         return false;
210     }
211 }