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              // Just use the maximum value we can represent via a Long.
75              maxFieldSectionSize = Long.MAX_VALUE;
76          }
77          this.maxTableCapacity = localSettings.getOrDefault(HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0);
78          int maxBlockedStreams = toIntExact(localSettings.getOrDefault(HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS, 0));
79          qpackDecoder = new QpackDecoder(maxTableCapacity, maxBlockedStreams);
80          qpackEncoder = new QpackEncoder();
81          codecFactory = Http3FrameCodec.newFactory(qpackDecoder, maxFieldSectionSize, qpackEncoder);
82          remoteControlStreamHandler =  new Http3ControlStreamOutboundHandler(server, localSettings,
83                  codecFactory.newCodec(Http3FrameTypeValidator.NO_VALIDATION, NO_STATE, NO_STATE));
84          localControlStreamHandler = new Http3ControlStreamInboundHandler(server, inboundControlStreamHandler,
85                  qpackEncoder, remoteControlStreamHandler);
86      }
87  
88      private void createControlStreamIfNeeded(ChannelHandlerContext ctx) {
89          if (!controlStreamCreationInProgress && Http3.getLocalControlStream(ctx.channel()) == null) {
90              controlStreamCreationInProgress = true;
91              QuicChannel channel = (QuicChannel) ctx.channel();
92              // Once the channel became active we need to create an unidirectional stream and write the
93              // Http3SettingsFrame to it. This needs to be the first frame on this stream.
94              // https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.2.1.
95              channel.createStream(QuicStreamType.UNIDIRECTIONAL, remoteControlStreamHandler)
96                      .addListener(f -> {
97                          if (!f.isSuccess()) {
98                              ctx.fireExceptionCaught(new Http3Exception(Http3ErrorCode.H3_STREAM_CREATION_ERROR,
99                                      "Unable to open control stream", f.cause()));
100                             ctx.close();
101                         } else {
102                             Http3.setLocalControlStream(channel, (QuicStreamChannel) f.getNow());
103                         }
104                     });
105         }
106     }
107 
108     /**
109      * Returns {@code true} if we received a GOAWAY frame from the remote peer.
110      * @return {@code true} if we received the frame, {@code false} otherwise.
111      */
112     public final boolean isGoAwayReceived() {
113         return localControlStreamHandler.isGoAwayReceived();
114     }
115 
116     /**
117      * Returns a new codec that will encode and decode {@link Http3Frame}s for this HTTP/3 connection.
118      *
119      * @return a new codec.
120      */
121     final ChannelHandler newCodec(Http3RequestStreamCodecState encodeState,
122                                   Http3RequestStreamCodecState decodeState) {
123         return codecFactory.newCodec(Http3RequestStreamFrameTypeValidator.INSTANCE, encodeState, decodeState);
124     }
125 
126     final ChannelHandler newRequestStreamValidationHandler(
127             QuicStreamChannel forStream, Http3RequestStreamCodecState encodeState,
128             Http3RequestStreamCodecState decodeState) {
129         final QpackAttributes qpackAttributes = Http3.getQpackAttributes(forStream.parent());
130         assert qpackAttributes != null;
131         if (localControlStreamHandler.isServer()) {
132             return Http3RequestStreamValidationHandler.newServerValidator(qpackAttributes, qpackDecoder,
133                     encodeState, decodeState);
134         }
135         return Http3RequestStreamValidationHandler.newClientValidator(localControlStreamHandler::isGoAwayReceived,
136                 qpackAttributes, qpackDecoder, encodeState, decodeState);
137     }
138 
139     final ChannelHandler newPushStreamValidationHandler(QuicStreamChannel forStream,
140                                                         Http3RequestStreamCodecState decodeState) {
141         if (localControlStreamHandler.isServer()) {
142             return Http3PushStreamServerValidationHandler.INSTANCE;
143         }
144         final QpackAttributes qpackAttributes = Http3.getQpackAttributes(forStream.parent());
145         assert qpackAttributes != null;
146         return new Http3PushStreamClientValidationHandler(qpackAttributes, qpackDecoder, decodeState);
147     }
148 
149     @Override
150     public void handlerAdded(ChannelHandlerContext ctx) {
151         QuicChannel channel = (QuicChannel) ctx.channel();
152         Http3.setQpackAttributes(channel, new QpackAttributes(channel, disableQpackDynamicTable));
153         if (ctx.channel().isActive()) {
154             createControlStreamIfNeeded(ctx);
155         }
156     }
157 
158     @Override
159     public void channelActive(ChannelHandlerContext ctx) {
160         createControlStreamIfNeeded(ctx);
161 
162         ctx.fireChannelActive();
163     }
164 
165     @Override
166     public void channelRead(ChannelHandlerContext ctx, Object msg) {
167         if (msg instanceof QuicStreamChannel) {
168             QuicStreamChannel channel = (QuicStreamChannel) msg;
169             switch (channel.type()) {
170                 case BIDIRECTIONAL:
171                     initBidirectionalStream(ctx, channel);
172                     break;
173                 case UNIDIRECTIONAL:
174                     initUnidirectionalStream(ctx, channel);
175                     break;
176                 default:
177                     throw new Error();
178             }
179         }
180         ctx.fireChannelRead(msg);
181     }
182 
183     /**
184      * Called when an bidirectional stream is opened from the remote-peer.
185      *
186      * @param ctx           the {@link ChannelHandlerContext} of the parent {@link QuicChannel}.
187      * @param streamChannel the {@link QuicStreamChannel}.
188      */
189     abstract void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel);
190 
191     /**
192      * Called when an unidirectional stream is opened from the remote-peer.
193      *
194      * @param ctx           the {@link ChannelHandlerContext} of the parent {@link QuicChannel}.
195      * @param streamChannel the {@link QuicStreamChannel}.
196      */
197     abstract void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel);
198 
199     long maxTableCapacity() {
200         return maxTableCapacity;
201     }
202 
203     /**
204      * Always returns {@code false} as it keeps state.
205      */
206     @Override
207     public boolean isSharable() {
208         return false;
209     }
210 }