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.Channel;
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.HttpServerCodec;
25 import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
26 import io.netty.handler.ssl.SslHandler;
27 import io.netty.util.internal.logging.InternalLogger;
28 import io.netty.util.internal.logging.InternalLoggerFactory;
29
30 import java.util.List;
31
32 /**
33 * @deprecated Use {@link ApplicationProtocolNegotiationHandler} instead.
34 */
35 public abstract class SpdyOrHttpChooser extends ByteToMessageDecoder {
36
37 private static final InternalLogger logger = InternalLoggerFactory.getInstance(SpdyOrHttpChooser.class);
38
39 public enum SelectedProtocol {
40 SPDY_3_1("spdy/3.1"),
41 HTTP_1_1("http/1.1"),
42 HTTP_1_0("http/1.0");
43
44 private final String name;
45
46 SelectedProtocol(String defaultName) {
47 name = defaultName;
48 }
49
50 public String protocolName() {
51 return name;
52 }
53
54 /**
55 * Get an instance of this enum based on the protocol name returned by the NPN server provider
56 *
57 * @param name the protocol name
58 * @return the selected protocol or {@code null} if there is no match
59 */
60 public static SelectedProtocol protocol(String name) {
61 for (SelectedProtocol protocol : SelectedProtocol.values()) {
62 if (protocol.protocolName().equals(name)) {
63 return protocol;
64 }
65 }
66 return null;
67 }
68 }
69
70 protected SpdyOrHttpChooser() { }
71
72 @Override
73 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
74 if (configurePipeline(ctx)) {
75 // When we reached here we can remove this handler as its now clear
76 // what protocol we want to use
77 // from this point on. This will also take care of forward all
78 // messages.
79 ctx.pipeline().remove(this);
80 }
81 }
82
83 private boolean configurePipeline(ChannelHandlerContext ctx) {
84 // Get the SslHandler from the ChannelPipeline so we can obtain the
85 // SslEngine from it.
86 SslHandler handler = ctx.pipeline().get(SslHandler.class);
87 if (handler == null) {
88 // SslHandler is needed by SPDY by design.
89 throw new IllegalStateException("cannot find a SslHandler in the pipeline (required for SPDY)");
90 }
91
92 if (!handler.handshakeFuture().isDone()) {
93 return false;
94 }
95
96 SelectedProtocol protocol;
97 try {
98 protocol = selectProtocol(handler);
99 } catch (Exception e) {
100 throw new IllegalStateException("failed to get the selected protocol", e);
101 }
102
103 if (protocol == null) {
104 throw new IllegalStateException("unknown protocol");
105 }
106
107 switch (protocol) {
108 case SPDY_3_1:
109 try {
110 configureSpdy(ctx, SpdyVersion.SPDY_3_1);
111 } catch (Exception e) {
112 throw new IllegalStateException("failed to configure a SPDY pipeline", e);
113 }
114 break;
115 case HTTP_1_0:
116 case HTTP_1_1:
117 try {
118 configureHttp1(ctx);
119 } catch (Exception e) {
120 throw new IllegalStateException("failed to configure a HTTP/1 pipeline", e);
121 }
122 break;
123 }
124 return true;
125 }
126
127 /**
128 * Returns the {@link SelectedProtocol} for the current SSL session. By default, this method returns the first
129 * known protocol.
130 *
131 * @return the selected application-level protocol, or {@code null} if the application-level protocol name of
132 * the specified {@code sslHandler} is neither {@code "http/1.1"}, {@code "http/1.0"} nor {@code "spdy/3.1"}
133 */
134 protected SelectedProtocol selectProtocol(SslHandler sslHandler) throws Exception {
135 final String appProto = sslHandler.applicationProtocol();
136 return appProto != null? SelectedProtocol.protocol(appProto) : SelectedProtocol.HTTP_1_1;
137 }
138
139 /**
140 * Configures the {@link Channel} of the specified {@code ctx} for HTTP/2.
141 * <p>
142 * A typical implementation of this method will look like the following:
143 * <pre>
144 * {@link ChannelPipeline} p = ctx.pipeline();
145 * p.addLast(new {@link SpdyFrameCodec}(version));
146 * p.addLast(new {@link SpdySessionHandler}(version, true));
147 * p.addLast(new {@link SpdyHttpEncoder}(version));
148 * p.addLast(new {@link SpdyHttpDecoder}(version, <i>maxSpdyContentLength</i>));
149 * p.addLast(new {@link SpdyHttpResponseStreamIdHandler}());
150 * p.addLast(new <i>YourHttpRequestHandler</i>());
151 * </pre>
152 * </p>
153 */
154 protected abstract void configureSpdy(ChannelHandlerContext ctx, SpdyVersion version) throws Exception;
155
156 /**
157 * Configures the {@link Channel} of the specified {@code ctx} for HTTP/1.
158 * <p>
159 * A typical implementation of this method will look like the following:
160 * <pre>
161 * {@link ChannelPipeline} p = ctx.pipeline();
162 * p.addLast(new {@link HttpServerCodec}());
163 * p.addLast(new <i>YourHttpRequestHandler</i>());
164 * </pre>
165 * </p>
166 */
167 protected abstract void configureHttp1(ChannelHandlerContext ctx) throws Exception;
168
169 @Override
170 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
171 logger.warn("{} Failed to select the application-level protocol:", ctx.channel(), cause);
172 ctx.close();
173 }
174 }