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