View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  package io.netty.handler.codec.http2;
16  
17  import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
18  import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
19  import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
20  import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
21  import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
22  import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeader;
23  import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
24  import static io.netty.util.internal.ObjectUtil.checkNotNull;
25  import io.netty.buffer.ByteBuf;
26  import io.netty.buffer.Unpooled;
27  import io.netty.channel.ChannelHandlerContext;
28  import io.netty.handler.codec.AsciiString;
29  import io.netty.handler.codec.base64.Base64;
30  import io.netty.handler.codec.http.FullHttpRequest;
31  import io.netty.handler.codec.http.FullHttpResponse;
32  import io.netty.handler.codec.http.HttpServerUpgradeHandler;
33  import io.netty.util.CharsetUtil;
34  
35  import java.util.Collection;
36  import java.util.Collections;
37  import java.util.List;
38  
39  /**
40   * Server-side codec for performing a cleartext upgrade from HTTP/1.x to HTTP/2.
41   */
42  public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.UpgradeCodec {
43  
44      private static final List<String> REQUIRED_UPGRADE_HEADERS =
45              Collections.singletonList(HTTP_UPGRADE_SETTINGS_HEADER);
46  
47      private final String handlerName;
48      private final Http2ConnectionHandler connectionHandler;
49      private final Http2FrameReader frameReader;
50  
51      /**
52       * Creates the codec using a default name for the connection handler when adding to the
53       * pipeline.
54       *
55       * @param connectionHandler the HTTP/2 connection handler.
56       */
57      public Http2ServerUpgradeCodec(Http2ConnectionHandler connectionHandler) {
58          this("http2ConnectionHandler", connectionHandler);
59      }
60  
61      /**
62       * Creates the codec providing an upgrade to the given handler for HTTP/2.
63       *
64       * @param handlerName the name of the HTTP/2 connection handler to be used in the pipeline.
65       * @param connectionHandler the HTTP/2 connection handler.
66       */
67      public Http2ServerUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler) {
68          this.handlerName = checkNotNull(handlerName, "handlerName");
69          this.connectionHandler = checkNotNull(connectionHandler, "connectionHandler");
70          frameReader = new DefaultHttp2FrameReader();
71      }
72  
73      @Override
74      public String protocol() {
75          return HTTP_UPGRADE_PROTOCOL_NAME;
76      }
77  
78      @Override
79      public Collection<String> requiredUpgradeHeaders() {
80          return REQUIRED_UPGRADE_HEADERS;
81      }
82  
83      @Override
84      public void prepareUpgradeResponse(ChannelHandlerContext ctx, FullHttpRequest upgradeRequest,
85              FullHttpResponse upgradeResponse) {
86          try {
87              // Decode the HTTP2-Settings header and set the settings on the handler to make
88              // sure everything is fine with the request.
89              List<CharSequence> upgradeHeaders = upgradeRequest.headers().getAll(HTTP_UPGRADE_SETTINGS_HEADER);
90              if (upgradeHeaders.isEmpty() || upgradeHeaders.size() > 1) {
91                  throw new IllegalArgumentException("There must be 1 and only 1 "
92                          + HTTP_UPGRADE_SETTINGS_HEADER + " header.");
93              }
94              Http2Settings settings = decodeSettingsHeader(ctx, upgradeHeaders.get(0));
95              connectionHandler.onHttpServerUpgrade(settings);
96              // Everything looks good, no need to modify the response.
97          } catch (Throwable e) {
98              // Send a failed response back to the client.
99              upgradeResponse.setStatus(BAD_REQUEST);
100             upgradeResponse.headers().clear();
101         }
102     }
103 
104     @Override
105     public void upgradeTo(final ChannelHandlerContext ctx, FullHttpRequest upgradeRequest,
106             FullHttpResponse upgradeResponse) {
107         // Add the HTTP/2 connection handler to the pipeline immediately following the current
108         // handler.
109         ctx.pipeline().addAfter(ctx.name(), handlerName, connectionHandler);
110     }
111 
112     /**
113      * Decodes the settings header and returns a {@link Http2Settings} object.
114      */
115     private Http2Settings decodeSettingsHeader(ChannelHandlerContext ctx, CharSequence settingsHeader)
116             throws Http2Exception {
117         ByteBuf header = Unpooled.wrappedBuffer(AsciiString.getBytes(settingsHeader, CharsetUtil.UTF_8));
118         try {
119             // Decode the SETTINGS payload.
120             ByteBuf payload = Base64.decode(header, URL_SAFE);
121 
122             // Create an HTTP/2 frame for the settings.
123             ByteBuf frame = createSettingsFrame(ctx, payload);
124 
125             // Decode the SETTINGS frame and return the settings object.
126             return decodeSettings(ctx, frame);
127         } finally {
128             header.release();
129         }
130     }
131 
132     /**
133      * Decodes the settings frame and returns the settings.
134      */
135     private Http2Settings decodeSettings(ChannelHandlerContext ctx, ByteBuf frame) throws Http2Exception {
136         try {
137             final Http2Settings decodedSettings = new Http2Settings();
138             frameReader.readFrame(ctx, frame, new Http2FrameAdapter() {
139                 @Override
140                 public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
141                     decodedSettings.copyFrom(settings);
142                 }
143             });
144             return decodedSettings;
145         } finally {
146             frame.release();
147         }
148     }
149 
150     /**
151      * Creates an HTTP2-Settings header with the given payload. The payload buffer is released.
152      */
153     private static ByteBuf createSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload) {
154         ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
155         writeFrameHeader(frame, payload.readableBytes(), SETTINGS, new Http2Flags(), 0);
156         frame.writeBytes(payload);
157         payload.release();
158         return frame;
159     }
160 }