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 java.lang.Math.toIntExact;
31  
32  /**
33   * Handler that handles <a href="https://datatracker.ietf.org/doc/html/rfc9114">HTTP3</a> connections.
34   */
35  public abstract class Http3ConnectionHandler extends ChannelInboundHandlerAdapter {
36  
37      final Http3FrameCodecFactory codecFactory;
38      final LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory;
39      final boolean disableQpackDynamicTable;
40      final Http3ControlStreamInboundHandler localControlStreamHandler;
41      final Http3ControlStreamOutboundHandler remoteControlStreamHandler;
42      final QpackDecoder qpackDecoder;
43      final QpackEncoder qpackEncoder;
44      final Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator;
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       * @param maxUnknownFramePayloadLength          the maximum payload size of an unknown frame.
62       */
63      Http3ConnectionHandler(boolean server, @Nullable ChannelHandler inboundControlStreamHandler,
64                             @Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
65                             @Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable,
66                             @Nullable Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator,
67                             int maxUnknownFramePayloadLength) {
68          this(server, inboundControlStreamHandler, unknownInboundStreamHandlerFactory, localSettings,
69                  disableQpackDynamicTable, nonStandardSettingsValidator, null, maxUnknownFramePayloadLength);
70      }
71  
72      Http3ConnectionHandler(boolean server, @Nullable ChannelHandler inboundControlStreamHandler,
73                             @Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
74                             @Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable,
75                             @Nullable Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator,
76                             @Nullable QpackSensitivityDetector sensitivityDetector,
77                             int maxUnknownFramePayloadLength) {
78          this.unknownInboundStreamHandlerFactory = unknownInboundStreamHandlerFactory;
79          this.disableQpackDynamicTable = disableQpackDynamicTable;
80          if (nonStandardSettingsValidator != null) {
81              this.nonStandardSettingsValidator = nonStandardSettingsValidator;
82          } else {
83              this.nonStandardSettingsValidator = (id, value) -> false;
84          }
85          if (localSettings == null) {
86              localSettings = new DefaultHttp3SettingsFrame(Http3Settings.defaultSettings());
87          } else {
88              localSettings = DefaultHttp3SettingsFrame.copyOf(localSettings);
89          }
90          Long maxFieldSectionSize = localSettings
91                  .settings()
92                  .getOrDefault(
93                          Http3SettingIdentifier.HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE.id(),
94                          Http3CodecUtils.DEFAULT_MAX_FIELD_SECTION_SIZE
95                  );
96          this.maxTableCapacity = localSettings
97                  .settings()
98                  .getOrDefault(
99                          Http3SettingIdentifier.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY.id(),
100                         0L
101                 );
102         int maxBlockedStreams = toIntExact(localSettings
103                 .settings()
104                 .getOrDefault(
105                         Http3SettingIdentifier.HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS.id(),
106                         0L)
107         );
108         qpackDecoder = new QpackDecoder(maxTableCapacity, maxBlockedStreams);
109         qpackEncoder = new QpackEncoder(sensitivityDetector);
110         codecFactory = Http3FrameCodec.newFactory(
111                 qpackDecoder, maxFieldSectionSize, maxUnknownFramePayloadLength, qpackEncoder);
112         remoteControlStreamHandler =  new Http3ControlStreamOutboundHandler(server, localSettings,
113                 codecFactory.newCodec(Http3FrameTypeValidator.NO_VALIDATION, NO_STATE, NO_STATE,
114                         this.nonStandardSettingsValidator));
115         localControlStreamHandler = new Http3ControlStreamInboundHandler(server, inboundControlStreamHandler,
116                 qpackEncoder, remoteControlStreamHandler);
117     }
118 
119     private void createControlStreamIfNeeded(ChannelHandlerContext ctx) {
120         if (!controlStreamCreationInProgress && Http3.getLocalControlStream(ctx.channel()) == null) {
121             controlStreamCreationInProgress = true;
122             QuicChannel channel = (QuicChannel) ctx.channel();
123             // Once the channel became active we need to create a unidirectional stream and write the
124             // Http3SettingsFrame to it. This needs to be the first frame on this stream.
125             // https://datatracker.ietf.org/doc/html/rfc9114#name-control-streams
126             channel.createStream(QuicStreamType.UNIDIRECTIONAL, remoteControlStreamHandler)
127                     .addListener(f -> {
128                         if (!f.isSuccess()) {
129                             ctx.fireExceptionCaught(new Http3Exception(Http3ErrorCode.H3_STREAM_CREATION_ERROR,
130                                     "Unable to open control stream", f.cause()));
131                             ctx.close();
132                         } else {
133                             Http3.setLocalControlStream(channel, (QuicStreamChannel) f.getNow());
134                         }
135                     });
136         }
137     }
138 
139     /**
140      * Returns {@code true} if we received a GOAWAY frame from the remote peer.
141      * @return {@code true} if we received the frame, {@code false} otherwise.
142      */
143     public final boolean isGoAwayReceived() {
144         return localControlStreamHandler.isGoAwayReceived();
145     }
146 
147     /**
148      * Returns a new codec that will encode and decode {@link Http3Frame}s for this HTTP/3 connection.
149      *
150      * @return a new codec.
151      */
152     final ChannelHandler newCodec(Http3RequestStreamCodecState encodeState,
153                                   Http3RequestStreamCodecState decodeState) {
154         return codecFactory.newCodec(
155                 Http3RequestStreamFrameTypeValidator.INSTANCE, encodeState, decodeState, nonStandardSettingsValidator);
156     }
157 
158     final ChannelHandler newRequestStreamValidationHandler(
159             QuicStreamChannel forStream, Http3RequestStreamCodecState encodeState,
160             Http3RequestStreamCodecState decodeState) {
161         final QpackAttributes qpackAttributes = Http3.getQpackAttributes(forStream.parent());
162         assert qpackAttributes != null;
163         if (localControlStreamHandler.isServer()) {
164             return Http3RequestStreamValidationHandler.newServerValidator(qpackAttributes, qpackDecoder,
165                     encodeState, decodeState);
166         }
167         return Http3RequestStreamValidationHandler.newClientValidator(localControlStreamHandler::isGoAwayReceived,
168                 qpackAttributes, qpackDecoder, encodeState, decodeState);
169     }
170 
171     final ChannelHandler newPushStreamValidationHandler(QuicStreamChannel forStream,
172                                                         Http3RequestStreamCodecState decodeState) {
173         if (localControlStreamHandler.isServer()) {
174             return Http3PushStreamServerValidationHandler.INSTANCE;
175         }
176         final QpackAttributes qpackAttributes = Http3.getQpackAttributes(forStream.parent());
177         assert qpackAttributes != null;
178         return new Http3PushStreamClientValidationHandler(qpackAttributes, qpackDecoder, decodeState);
179     }
180 
181     @Override
182     public void handlerAdded(ChannelHandlerContext ctx) {
183         QuicChannel channel = (QuicChannel) ctx.channel();
184         Http3.setQpackAttributes(channel, new QpackAttributes(channel, disableQpackDynamicTable));
185         if (ctx.channel().isActive()) {
186             createControlStreamIfNeeded(ctx);
187         }
188     }
189 
190     @Override
191     public void channelActive(ChannelHandlerContext ctx) {
192         createControlStreamIfNeeded(ctx);
193 
194         ctx.fireChannelActive();
195     }
196 
197     @Override
198     public void channelRead(ChannelHandlerContext ctx, Object msg) {
199         if (msg instanceof QuicStreamChannel) {
200             QuicStreamChannel channel = (QuicStreamChannel) msg;
201             switch (channel.type()) {
202                 case BIDIRECTIONAL:
203                     initBidirectionalStream(ctx, channel);
204                     break;
205                 case UNIDIRECTIONAL:
206                     initUnidirectionalStream(ctx, channel);
207                     break;
208                 default:
209                     throw new Error("Unexpected channel type: " + channel.type());
210             }
211         }
212         ctx.fireChannelRead(msg);
213     }
214 
215     /**
216      * Called when an bidirectional stream is opened from the remote-peer.
217      *
218      * @param ctx           the {@link ChannelHandlerContext} of the parent {@link QuicChannel}.
219      * @param streamChannel the {@link QuicStreamChannel}.
220      */
221     abstract void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel);
222 
223     /**
224      * Called when an unidirectional stream is opened from the remote-peer.
225      *
226      * @param ctx           the {@link ChannelHandlerContext} of the parent {@link QuicChannel}.
227      * @param streamChannel the {@link QuicStreamChannel}.
228      */
229     abstract void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel);
230 
231     long maxTableCapacity() {
232         return maxTableCapacity;
233     }
234 
235     /**
236      * Always returns {@code false} as it keeps state.
237      */
238     @Override
239     public boolean isSharable() {
240         return false;
241     }
242 }