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.spdy;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelHandler;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelPipeline;
22  import io.netty.handler.codec.ByteToMessageDecoder;
23  import io.netty.handler.codec.http.HttpObjectAggregator;
24  import io.netty.handler.codec.http.HttpRequestDecoder;
25  import io.netty.handler.codec.http.HttpResponseEncoder;
26  import io.netty.handler.ssl.SslHandler;
27  import io.netty.util.internal.StringUtil;
28  
29  import javax.net.ssl.SSLEngine;
30  import java.util.List;
31  
32  /**
33   * {@link ChannelHandler} which is responsible to setup the {@link ChannelPipeline} either for HTTP or SPDY. This offers
34   * an easy way for users to support both at the same time while not care to much about the low-level details.
35   */
36  public abstract class SpdyOrHttpChooser extends ByteToMessageDecoder {
37  
38      // TODO: Replace with generic NPN handler
39  
40      public enum SelectedProtocol {
41          SPDY_3_1("spdy/3.1"),
42          HTTP_1_1("http/1.1"),
43          HTTP_1_0("http/1.0"),
44          UNKNOWN("Unknown");
45  
46          private final String name;
47  
48          SelectedProtocol(String defaultName) {
49              name = defaultName;
50          }
51  
52          public String protocolName() {
53              return name;
54          }
55  
56          /**
57           * Get an instance of this enum based on the protocol name returned by the NPN server provider
58           *
59           * @param name
60           *            the protocol name
61           * @return the SelectedProtocol instance
62           */
63          public static SelectedProtocol protocol(String name) {
64              for (SelectedProtocol protocol : SelectedProtocol.values()) {
65                  if (protocol.protocolName().equals(name)) {
66                      return protocol;
67                  }
68              }
69              return UNKNOWN;
70          }
71      }
72  
73      private final int maxSpdyContentLength;
74      private final int maxHttpContentLength;
75  
76      protected SpdyOrHttpChooser(int maxSpdyContentLength, int maxHttpContentLength) {
77          this.maxSpdyContentLength = maxSpdyContentLength;
78          this.maxHttpContentLength = maxHttpContentLength;
79      }
80  
81      /**
82       * Return the {@link SelectedProtocol} for the {@link SSLEngine}. If its not known yet implementations MUST return
83       * {@link SelectedProtocol#UNKNOWN}.
84       *
85       */
86      protected SelectedProtocol getProtocol(SSLEngine engine) {
87          String[] protocol = StringUtil.split(engine.getSession().getProtocol(), ':');
88          if (protocol.length < 2) {
89              // Use HTTP/1.1 as default
90              return SelectedProtocol.HTTP_1_1;
91          }
92          SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]);
93          return selectedProtocol;
94      }
95  
96      @Override
97      protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
98          if (initPipeline(ctx)) {
99              // When we reached here we can remove this handler as its now clear
100             // what protocol we want to use
101             // from this point on. This will also take care of forward all
102             // messages.
103             ctx.pipeline().remove(this);
104         }
105     }
106 
107     private boolean initPipeline(ChannelHandlerContext ctx) {
108         // Get the SslHandler from the ChannelPipeline so we can obtain the
109         // SslEngine from it.
110         SslHandler handler = ctx.pipeline().get(SslHandler.class);
111         if (handler == null) {
112             // SslHandler is needed by SPDY by design.
113             throw new IllegalStateException("SslHandler is needed for SPDY");
114         }
115 
116         SelectedProtocol protocol = getProtocol(handler.engine());
117         switch (protocol) {
118         case UNKNOWN:
119             // Not done with choosing the protocol, so just return here for now,
120             return false;
121         case SPDY_3_1:
122             addSpdyHandlers(ctx, SpdyVersion.SPDY_3_1);
123             break;
124         case HTTP_1_0:
125         case HTTP_1_1:
126             addHttpHandlers(ctx);
127             break;
128         default:
129             throw new IllegalStateException("Unknown SelectedProtocol");
130         }
131         return true;
132     }
133 
134     /**
135      * Add all {@link ChannelHandler}'s that are needed for SPDY with the given version.
136      */
137     protected void addSpdyHandlers(ChannelHandlerContext ctx, SpdyVersion version) {
138         ChannelPipeline pipeline = ctx.pipeline();
139         pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(version));
140         pipeline.addLast("spdySessionHandler", new SpdySessionHandler(version, true));
141         pipeline.addLast("spdyHttpEncoder", new SpdyHttpEncoder(version));
142         pipeline.addLast("spdyHttpDecoder", new SpdyHttpDecoder(version, maxSpdyContentLength));
143         pipeline.addLast("spdyStreamIdHandler", new SpdyHttpResponseStreamIdHandler());
144         pipeline.addLast("httpRequestHandler", createHttpRequestHandlerForSpdy());
145     }
146 
147     /**
148      * Add all {@link ChannelHandler}'s that are needed for HTTP.
149      */
150     protected void addHttpHandlers(ChannelHandlerContext ctx) {
151         ChannelPipeline pipeline = ctx.pipeline();
152         pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder());
153         pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder());
154         pipeline.addLast("httpChunkAggregator", new HttpObjectAggregator(maxHttpContentLength));
155         pipeline.addLast("httpRequestHandler", createHttpRequestHandlerForHttp());
156     }
157 
158     /**
159      * Create the {@link ChannelHandler} that is responsible for handling the http requests when the
160      * {@link SelectedProtocol} was {@link SelectedProtocol#HTTP_1_0} or {@link SelectedProtocol#HTTP_1_1}
161      */
162     protected abstract ChannelHandler createHttpRequestHandlerForHttp();
163 
164     /**
165      * Create the {@link ChannelHandler} that is responsible for handling the http responses when the
166      * when the {@link SelectedProtocol} was {@link SelectedProtocol#SPDY_3_1}.
167      *
168      * By default this getMethod will just delecate to {@link #createHttpRequestHandlerForHttp()}, but sub-classes may
169      * override this to change the behaviour.
170      */
171     protected ChannelHandler createHttpRequestHandlerForSpdy() {
172         return createHttpRequestHandlerForHttp();
173     }
174 }