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    * https://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  
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.List;
29  
30  import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
31  import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
32  import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
33  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
34  import static io.netty.util.CharsetUtil.UTF_8;
35  import static io.netty.util.ReferenceCountUtil.release;
36  import static io.netty.util.internal.ObjectUtil.checkNotNull;
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<CharSequence> UPGRADE_HEADERS = Collections.singletonList(HTTP_UPGRADE_SETTINGS_HEADER);
44  
45      private final String handlerName;
46      private final Http2ConnectionHandler connectionHandler;
47      private final ChannelHandler upgradeToHandler;
48      private final ChannelHandler http2MultiplexHandler;
49  
50      public Http2ClientUpgradeCodec(Http2FrameCodec frameCodec, ChannelHandler upgradeToHandler) {
51          this(null, frameCodec, upgradeToHandler);
52      }
53  
54      public Http2ClientUpgradeCodec(String handlerName, Http2FrameCodec frameCodec, ChannelHandler upgradeToHandler) {
55          this(handlerName, (Http2ConnectionHandler) frameCodec, upgradeToHandler, null);
56      }
57  
58      /**
59       * Creates the codec using a default name for the connection handler when adding to the
60       * pipeline.
61       *
62       * @param connectionHandler the HTTP/2 connection handler
63       */
64      public Http2ClientUpgradeCodec(Http2ConnectionHandler connectionHandler) {
65          this((String) null, connectionHandler);
66      }
67  
68      /**
69       * Creates the codec using a default name for the connection handler when adding to the
70       * pipeline.
71       *
72       * @param connectionHandler the HTTP/2 connection handler
73       * @param http2MultiplexHandler the Http2 Multiplexer handler to work with Http2FrameCodec
74       */
75      public Http2ClientUpgradeCodec(Http2ConnectionHandler connectionHandler,
76          Http2MultiplexHandler http2MultiplexHandler) {
77          this((String) null, connectionHandler, http2MultiplexHandler);
78      }
79  
80      /**
81       * Creates the codec providing an upgrade to the given handler for HTTP/2.
82       *
83       * @param handlerName the name of the HTTP/2 connection handler to be used in the pipeline,
84       *                    or {@code null} to auto-generate the name
85       * @param connectionHandler the HTTP/2 connection handler
86       */
87      public Http2ClientUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler) {
88          this(handlerName, connectionHandler, connectionHandler, null);
89      }
90  
91      /**
92       * Creates the codec providing an upgrade to the given handler for HTTP/2.
93       *
94       * @param handlerName the name of the HTTP/2 connection handler to be used in the pipeline,
95       *                    or {@code null} to auto-generate the name
96       * @param connectionHandler the HTTP/2 connection handler
97       */
98      public Http2ClientUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler,
99          Http2MultiplexHandler http2MultiplexHandler) {
100         this(handlerName, connectionHandler, connectionHandler, http2MultiplexHandler);
101     }
102 
103     private Http2ClientUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler, ChannelHandler
104         upgradeToHandler, Http2MultiplexHandler http2MultiplexHandler) {
105         this.handlerName = handlerName;
106         this.connectionHandler = checkNotNull(connectionHandler, "connectionHandler");
107         this.upgradeToHandler = checkNotNull(upgradeToHandler, "upgradeToHandler");
108         this.http2MultiplexHandler = http2MultiplexHandler;
109     }
110 
111     @Override
112     public CharSequence protocol() {
113         return HTTP_UPGRADE_PROTOCOL_NAME;
114     }
115 
116     @Override
117     public Collection<CharSequence> setUpgradeHeaders(ChannelHandlerContext ctx,
118         HttpRequest upgradeRequest) {
119         CharSequence settingsValue = getSettingsHeaderValue(ctx);
120         upgradeRequest.headers().set(HTTP_UPGRADE_SETTINGS_HEADER, settingsValue);
121         return UPGRADE_HEADERS;
122     }
123 
124     @Override
125     public void upgradeTo(ChannelHandlerContext ctx, FullHttpResponse upgradeResponse)
126         throws Exception {
127         try {
128             // Add the handler to the pipeline.
129             ctx.pipeline().addAfter(ctx.name(), handlerName, upgradeToHandler);
130 
131             // Add the Http2 Multiplex handler as this handler handle events produced by the connectionHandler.
132             // See https://github.com/netty/netty/issues/9495
133             if (http2MultiplexHandler != null) {
134                 final String name = ctx.pipeline().context(connectionHandler).name();
135                 ctx.pipeline().addAfter(name, null, http2MultiplexHandler);
136             }
137 
138             // Reserve local stream 1 for the response.
139             connectionHandler.onHttpClientUpgrade();
140         } catch (Http2Exception e) {
141             ctx.fireExceptionCaught(e);
142             ctx.close();
143         }
144     }
145 
146     /**
147      * Converts the current settings for the handler to the Base64-encoded representation used in
148      * the HTTP2-Settings upgrade header.
149      */
150     private CharSequence getSettingsHeaderValue(ChannelHandlerContext ctx) {
151         ByteBuf buf = null;
152         ByteBuf encodedBuf = null;
153         try {
154             // Get the local settings for the handler.
155             Http2Settings settings = connectionHandler.decoder().localSettings();
156 
157             // Serialize the payload of the SETTINGS frame.
158             int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
159             buf = ctx.alloc().buffer(payloadLength);
160             for (CharObjectMap.PrimitiveEntry<Long> entry : settings.entries()) {
161                 buf.writeChar(entry.key());
162                 buf.writeInt(entry.value().intValue());
163             }
164 
165             // Base64 encode the payload and then convert to a string for the header.
166             encodedBuf = Base64.encode(buf, URL_SAFE);
167             return encodedBuf.toString(UTF_8);
168         } finally {
169             release(buf);
170             release(encodedBuf);
171         }
172     }
173 }