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