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.example.http2.helloworld.client;
16  
17  import io.netty.channel.ChannelHandlerContext;
18  import io.netty.channel.ChannelInboundHandlerAdapter;
19  import io.netty.channel.ChannelInitializer;
20  import io.netty.channel.ChannelPipeline;
21  import io.netty.channel.socket.SocketChannel;
22  import io.netty.handler.codec.http.DefaultFullHttpRequest;
23  import io.netty.handler.codec.http.HttpClientCodec;
24  import io.netty.handler.codec.http.HttpClientUpgradeHandler;
25  import io.netty.handler.codec.http.HttpMethod;
26  import io.netty.handler.codec.http.HttpVersion;
27  import io.netty.handler.codec.http2.DefaultHttp2Connection;
28  import io.netty.handler.codec.http2.DelegatingDecompressorFrameListener;
29  import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
30  import io.netty.handler.codec.http2.Http2Connection;
31  import io.netty.handler.codec.http2.Http2FrameLogger;
32  import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler;
33  import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder;
34  import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder;
35  import io.netty.handler.ssl.ApplicationProtocolNames;
36  import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
37  import io.netty.handler.ssl.SslContext;
38  
39  import static io.netty.handler.logging.LogLevel.INFO;
40  
41  /**
42   * Configures the client pipeline to support HTTP/2 frames.
43   */
44  public class Http2ClientInitializer extends ChannelInitializer<SocketChannel> {
45      private static final Http2FrameLogger logger = new Http2FrameLogger(INFO, Http2ClientInitializer.class);
46  
47      private final SslContext sslCtx;
48      private final int maxContentLength;
49      private HttpToHttp2ConnectionHandler connectionHandler;
50      private HttpResponseHandler responseHandler;
51      private Http2SettingsHandler settingsHandler;
52  
53      public Http2ClientInitializer(SslContext sslCtx, int maxContentLength) {
54          this.sslCtx = sslCtx;
55          this.maxContentLength = maxContentLength;
56      }
57  
58      @Override
59      public void initChannel(SocketChannel ch) throws Exception {
60          final Http2Connection connection = new DefaultHttp2Connection(false);
61          connectionHandler = new HttpToHttp2ConnectionHandlerBuilder()
62                  .frameListener(new DelegatingDecompressorFrameListener(
63                          connection,
64                          new InboundHttp2ToHttpAdapterBuilder(connection)
65                                  .maxContentLength(maxContentLength)
66                                  .propagateSettings(true)
67                                  .build()))
68                  .frameLogger(logger)
69                  .connection(connection)
70                  .build();
71          responseHandler = new HttpResponseHandler();
72          settingsHandler = new Http2SettingsHandler(ch.newPromise());
73          if (sslCtx != null) {
74              configureSsl(ch);
75          } else {
76              configureClearText(ch);
77          }
78      }
79  
80      public HttpResponseHandler responseHandler() {
81          return responseHandler;
82      }
83  
84      public Http2SettingsHandler settingsHandler() {
85          return settingsHandler;
86      }
87  
88      protected void configureEndOfPipeline(ChannelPipeline pipeline) {
89          pipeline.addLast(settingsHandler, responseHandler);
90      }
91  
92      /**
93       * Configure the pipeline for TLS NPN negotiation to HTTP/2.
94       */
95      private void configureSsl(SocketChannel ch) {
96          ChannelPipeline pipeline = ch.pipeline();
97          pipeline.addLast(sslCtx.newHandler(ch.alloc()));
98          // We must wait for the handshake to finish and the protocol to be negotiated before configuring
99          // the HTTP/2 components of the pipeline.
100         pipeline.addLast(new ApplicationProtocolNegotiationHandler("") {
101             @Override
102             protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
103                 if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
104                     ChannelPipeline p = ctx.pipeline();
105                     p.addLast(connectionHandler);
106                     configureEndOfPipeline(p);
107                     return;
108                 }
109                 ctx.close();
110                 throw new IllegalStateException("unknown protocol: " + protocol);
111             }
112         });
113     }
114 
115     /**
116      * Configure the pipeline for a cleartext upgrade from HTTP to HTTP/2.
117      */
118     private void configureClearText(SocketChannel ch) {
119         HttpClientCodec sourceCodec = new HttpClientCodec();
120         Http2ClientUpgradeCodec upgradeCodec = new Http2ClientUpgradeCodec(connectionHandler);
121         HttpClientUpgradeHandler upgradeHandler = new HttpClientUpgradeHandler(sourceCodec, upgradeCodec, 65536);
122 
123         ch.pipeline().addLast(sourceCodec,
124                               upgradeHandler,
125                               new UpgradeRequestHandler(),
126                               new UserEventLogger());
127     }
128 
129     /**
130      * A handler that triggers the cleartext upgrade to HTTP/2 by sending an initial HTTP request.
131      */
132     private final class UpgradeRequestHandler extends ChannelInboundHandlerAdapter {
133         @Override
134         public void channelActive(ChannelHandlerContext ctx) throws Exception {
135             DefaultFullHttpRequest upgradeRequest =
136                     new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
137             ctx.writeAndFlush(upgradeRequest);
138 
139             ctx.fireChannelActive();
140 
141             // Done with this handler, remove it from the pipeline.
142             ctx.pipeline().remove(this);
143 
144             configureEndOfPipeline(ctx.pipeline());
145         }
146     }
147 
148     /**
149      * Class that logs any User Events triggered on this channel.
150      */
151     private static class UserEventLogger extends ChannelInboundHandlerAdapter {
152         @Override
153         public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
154             System.out.println("User Event Triggered: " + evt);
155             ctx.fireUserEventTriggered(evt);
156         }
157     }
158 }