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