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.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
19  import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
20  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
21  import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt;
22  import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort;
23  import static io.netty.util.CharsetUtil.UTF_8;
24  import static io.netty.util.ReferenceCountUtil.release;
25  import static io.netty.util.internal.ObjectUtil.checkNotNull;
26  import io.netty.buffer.ByteBuf;
27  import io.netty.channel.ChannelHandlerContext;
28  import io.netty.handler.codec.base64.Base64;
29  import io.netty.handler.codec.http.FullHttpResponse;
30  import io.netty.handler.codec.http.HttpClientUpgradeHandler;
31  import io.netty.handler.codec.http.HttpRequest;
32  import io.netty.util.collection.IntObjectMap;
33  
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.List;
37  
38  /**
39   * Client-side cleartext upgrade codec from HTTP to HTTP/2.
40   */
41  public class Http2ClientUpgradeCodec implements HttpClientUpgradeHandler.UpgradeCodec {
42  
43      private static final List<String> UPGRADE_HEADERS = Collections.singletonList(HTTP_UPGRADE_SETTINGS_HEADER);
44  
45      private final String handlerName;
46      private final Http2ConnectionHandler connectionHandler;
47  
48      /**
49       * Creates the codec using a default name for the connection handler when adding to the
50       * pipeline.
51       *
52       * @param connectionHandler the HTTP/2 connection handler.
53       */
54      public Http2ClientUpgradeCodec(Http2ConnectionHandler connectionHandler) {
55          this("http2ConnectionHandler", connectionHandler);
56      }
57  
58      /**
59       * Creates the codec providing an upgrade to the given handler for HTTP/2.
60       *
61       * @param handlerName the name of the HTTP/2 connection handler to be used in the pipeline.
62       * @param connectionHandler the HTTP/2 connection handler.
63       */
64      public Http2ClientUpgradeCodec(String handlerName,
65              Http2ConnectionHandler connectionHandler) {
66          this.handlerName = checkNotNull(handlerName, "handlerName");
67          this.connectionHandler = checkNotNull(connectionHandler, "connectionHandler");
68      }
69  
70      @Override
71      public String protocol() {
72          return HTTP_UPGRADE_PROTOCOL_NAME;
73      }
74  
75      @Override
76      public Collection<String> setUpgradeHeaders(ChannelHandlerContext ctx,
77              HttpRequest upgradeRequest) {
78          String settingsValue = getSettingsHeaderValue(ctx);
79          upgradeRequest.headers().set(HTTP_UPGRADE_SETTINGS_HEADER, settingsValue);
80          return UPGRADE_HEADERS;
81      }
82  
83      @Override
84      public void upgradeTo(ChannelHandlerContext ctx, FullHttpResponse upgradeResponse)
85              throws Exception {
86          // Reserve local stream 1 for the response.
87          connectionHandler.onHttpClientUpgrade();
88  
89          // Add the handler to the pipeline.
90          ctx.pipeline().addAfter(ctx.name(), handlerName, connectionHandler);
91      }
92  
93      /**
94       * Converts the current settings for the handler to the Base64-encoded representation used in
95       * the HTTP2-Settings upgrade header.
96       */
97      private String getSettingsHeaderValue(ChannelHandlerContext ctx) {
98          ByteBuf buf = null;
99          ByteBuf encodedBuf = null;
100         try {
101             // Get the local settings for the handler.
102             Http2Settings settings = connectionHandler.decoder().localSettings();
103 
104             // Serialize the payload of the SETTINGS frame.
105             int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
106             buf = ctx.alloc().buffer(payloadLength);
107             for (IntObjectMap.Entry<Long> entry : settings.entries()) {
108                 writeUnsignedShort(entry.key(), buf);
109                 writeUnsignedInt(entry.value(), buf);
110             }
111 
112             // Base64 encode the payload and then convert to a string for the header.
113             encodedBuf = Base64.encode(buf, URL_SAFE);
114             return encodedBuf.toString(UTF_8);
115         } finally {
116             release(buf);
117             release(encodedBuf);
118         }
119     }
120 }