View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a 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
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http2;
17  
18  import static io.netty.handler.codec.http2.Http2CodecUtil.TLS_UPGRADE_PROTOCOL_NAME;
19  import io.netty.buffer.ByteBuf;
20  import io.netty.channel.ChannelHandler;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.ChannelPipeline;
23  import io.netty.handler.codec.ByteToMessageDecoder;
24  import io.netty.handler.codec.http.HttpObjectAggregator;
25  import io.netty.handler.codec.http.HttpRequestDecoder;
26  import io.netty.handler.codec.http.HttpResponseEncoder;
27  import io.netty.handler.ssl.SslHandler;
28  
29  import java.util.List;
30  
31  import javax.net.ssl.SSLEngine;
32  
33  /**
34   * {@link io.netty.channel.ChannelHandler} which is responsible to setup the
35   * {@link io.netty.channel.ChannelPipeline} either for HTTP or HTTP2. This offers an easy way for
36   * users to support both at the same time while not care to much about the low-level details.
37   */
38  public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
39  
40      public enum SelectedProtocol {
41          /** Must be updated to match the HTTP/2 draft number. */
42          HTTP_2(TLS_UPGRADE_PROTOCOL_NAME),
43          HTTP_1_1("http/1.1"),
44          HTTP_1_0("http/1.0"),
45          UNKNOWN("Unknown");
46  
47          private final String name;
48  
49          SelectedProtocol(String defaultName) {
50              name = defaultName;
51          }
52  
53          public String protocolName() {
54              return name;
55          }
56  
57          /**
58           * Get an instance of this enum based on the protocol name returned by the NPN server provider
59           *
60           * @param name
61           *            the protocol name
62           * @return the SelectedProtocol instance
63           */
64          public static SelectedProtocol protocol(String name) {
65              for (SelectedProtocol protocol : SelectedProtocol.values()) {
66                  if (protocol.protocolName().equals(name)) {
67                      return protocol;
68                  }
69              }
70              return UNKNOWN;
71          }
72      }
73  
74      private final int maxHttpContentLength;
75  
76      protected Http2OrHttpChooser(int maxHttpContentLength) {
77          this.maxHttpContentLength = maxHttpContentLength;
78      }
79  
80      /**
81       * Return the {@link SelectedProtocol} for the {@link javax.net.ssl.SSLEngine}. If its not known
82       * yet implementations MUST return {@link SelectedProtocol#UNKNOWN}.
83       */
84      protected abstract SelectedProtocol getProtocol(SSLEngine engine);
85  
86      @Override
87      protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
88          if (initPipeline(ctx)) {
89              // When we reached here we can remove this handler as its now clear
90              // what protocol we want to use
91              // from this point on. This will also take care of forward all
92              // messages.
93              ctx.pipeline().remove(this);
94          }
95      }
96  
97      private boolean initPipeline(ChannelHandlerContext ctx) {
98          // Get the SslHandler from the ChannelPipeline so we can obtain the
99          // SslEngine from it.
100         SslHandler handler = ctx.pipeline().get(SslHandler.class);
101         if (handler == null) {
102             // HTTP2 is negotiated through SSL.
103             throw new IllegalStateException("SslHandler is needed for HTTP2");
104         }
105 
106         SelectedProtocol protocol = getProtocol(handler.engine());
107         switch (protocol) {
108             case UNKNOWN:
109                 // Not done with choosing the protocol, so just return here for now,
110                 return false;
111             case HTTP_2:
112                 addHttp2Handlers(ctx);
113                 break;
114             case HTTP_1_0:
115             case HTTP_1_1:
116                 addHttpHandlers(ctx);
117                 break;
118             default:
119                 throw new IllegalStateException("Unknown SelectedProtocol");
120         }
121         return true;
122     }
123 
124     /**
125      * Add all {@link io.netty.channel.ChannelHandler}'s that are needed for HTTP_2.
126      */
127     protected void addHttp2Handlers(ChannelHandlerContext ctx) {
128         ChannelPipeline pipeline = ctx.pipeline();
129         pipeline.addLast("http2ConnectionHandler", createHttp2RequestHandler());
130     }
131 
132     /**
133      * Add all {@link io.netty.channel.ChannelHandler}'s that are needed for HTTP.
134      */
135     protected void addHttpHandlers(ChannelHandlerContext ctx) {
136         ChannelPipeline pipeline = ctx.pipeline();
137         pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder());
138         pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder());
139         pipeline.addLast("httpChunkAggregator", new HttpObjectAggregator(maxHttpContentLength));
140         pipeline.addLast("httpRequestHandler", createHttp1RequestHandler());
141     }
142 
143     /**
144      * Create the {@link io.netty.channel.ChannelHandler} that is responsible for handling the http
145      * requests when the {@link SelectedProtocol} was {@link SelectedProtocol#HTTP_1_0} or
146      * {@link SelectedProtocol#HTTP_1_1}
147      */
148     protected abstract ChannelHandler createHttp1RequestHandler();
149 
150     /**
151      * Create the {@link ChannelHandler} that is responsible for handling the http responses
152      * when the when the {@link SelectedProtocol} was {@link SelectedProtocol#HTTP_2}.
153      */
154     protected abstract Http2ConnectionHandler createHttp2RequestHandler();
155 }