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