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.netty5.example.http2.helloworld.client;
16  
17  import io.netty5.channel.ChannelHandler;
18  import io.netty5.channel.ChannelHandlerContext;
19  import io.netty5.channel.ChannelInitializer;
20  import io.netty5.channel.ChannelPipeline;
21  import io.netty5.channel.socket.SocketChannel;
22  import io.netty5.handler.codec.http.DefaultFullHttpRequest;
23  import io.netty5.handler.codec.http.DefaultHttpContent;
24  import io.netty5.handler.codec.http.HttpClientCodec;
25  import io.netty5.handler.codec.http.HttpClientUpgradeHandler;
26  import io.netty5.handler.codec.http.HttpHeaderNames;
27  import io.netty5.handler.codec.http.HttpMethod;
28  import io.netty5.handler.codec.http.HttpVersion;
29  import io.netty5.handler.codec.http2.DefaultHttp2Connection;
30  import io.netty5.handler.codec.http2.DelegatingDecompressorFrameListener;
31  import io.netty5.handler.codec.http2.Http2ClientUpgradeCodec;
32  import io.netty5.handler.codec.http2.Http2Connection;
33  import io.netty5.handler.codec.http2.Http2FrameLogger;
34  import io.netty5.handler.codec.http2.HttpToHttp2ConnectionHandler;
35  import io.netty5.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder;
36  import io.netty5.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder;
37  import io.netty5.handler.ssl.ApplicationProtocolNames;
38  import io.netty5.handler.ssl.ApplicationProtocolNegotiationHandler;
39  import io.netty5.handler.ssl.SslContext;
40  
41  import java.net.InetSocketAddress;
42  
43  import static io.netty5.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.bufferAllocator(), 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 =
127                 new HttpClientUpgradeHandler<DefaultHttpContent>(sourceCodec, upgradeCodec, 65536);
128 
129         ch.pipeline().addLast(sourceCodec,
130                               upgradeHandler,
131                               new UpgradeRequestHandler(),
132                               new UserEventLogger());
133     }
134 
135     /**
136      * A handler that triggers the cleartext upgrade to HTTP/2 by sending an initial HTTP request.
137      */
138     private final class UpgradeRequestHandler implements ChannelHandler {
139 
140         @Override
141         public void channelActive(ChannelHandlerContext ctx) throws Exception {
142             DefaultFullHttpRequest upgradeRequest =
143                     new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/",
144                             ctx.bufferAllocator().allocate(0));
145 
146             // Set HOST header as the remote peer may require it.
147             InetSocketAddress remote = (InetSocketAddress) ctx.channel().remoteAddress();
148             String hostString = remote.getHostString();
149             if (hostString == null) {
150                 hostString = remote.getAddress().getHostAddress();
151             }
152             upgradeRequest.headers().set(HttpHeaderNames.HOST, hostString + ':' + remote.getPort());
153 
154             ctx.writeAndFlush(upgradeRequest);
155 
156             ctx.fireChannelActive();
157 
158             // Done with this handler, remove it from the pipeline.
159             ctx.pipeline().remove(this);
160 
161             configureEndOfPipeline(ctx.pipeline());
162         }
163     }
164 
165     /**
166      * Class that logs any User Events triggered on this channel.
167      */
168     private static class UserEventLogger implements ChannelHandler {
169         @Override
170         public void channelInboundEvent(ChannelHandlerContext ctx, Object evt) throws Exception {
171             System.out.println("User Event Triggered: " + evt);
172             ctx.fireChannelInboundEvent(evt);
173         }
174     }
175 }