From 077508949638bd90c11d81ec1ce3b0ab257e5eb6 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Mon, 1 Jun 2015 16:05:00 +0900 Subject: [PATCH] Replace SpdyOrHttpChooser and Http2OrHttpChooser with ApplicationProtocolNegotiationHandler Motivation: SpdyOrHttpChooser and Http2OrHttpChooser duplicate fair amount code with each other. Modification: - Replace SpdyOrHttpChooser and Http2OrHttpChooser with ApplicationProtocolNegotiationHandler - Add ApplicationProtocolNames to define the known application-level protocol names Result: - Less code duplication - A user can perform dynamic pipeline configuration that follows ALPN/NPN for any protocols. --- .../handler/codec/spdy/SpdyOrHttpChooser.java | 177 ------------------ .../handler/codec/http2/Http2CodecUtil.java | 3 +- .../codec/http2/Http2OrHttpChooser.java | 170 ----------------- .../http2/helloworld/client/Http2Client.java | 6 +- .../helloworld/server/Http2OrHttpHandler.java | 28 ++- .../http2/helloworld/server/Http2Server.java | 6 +- .../http2/tiles/Http2OrHttpHandler.java | 37 +++- .../example/http2/tiles/Http2Server.java | 23 +-- .../netty/example/spdy/client/SpdyClient.java | 6 +- .../spdy/server/SpdyOrHttpHandler.java | 28 ++- .../netty/example/spdy/server/SpdyServer.java | 6 +- .../ssl/ApplicationProtocolAccessor.java | 6 + .../handler/ssl/ApplicationProtocolNames.java | 59 ++++++ ...ApplicationProtocolNegotiationHandler.java | 125 +++++++++++++ .../handler/ssl/ApplicationProtocolUtil.java | 1 - .../ssl/SslHandshakeCompletionEvent.java | 8 +- 16 files changed, 288 insertions(+), 401 deletions(-) delete mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java delete mode 100644 codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNames.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java deleted file mode 100644 index e176ef1055..0000000000 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.handler.codec.spdy; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandler; -import io.netty.channel.ChannelPipeline; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.http.HttpServerCodec; -import io.netty.handler.ssl.SslHandler; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; - -import java.util.List; - -/** - * {@link ChannelInboundHandler} which is responsible to setup the {@link ChannelPipeline} either for - * HTTP or SPDY. This offers an easy way for users to support both at the same time while not care to - * much about the low-level details. - */ -public abstract class SpdyOrHttpChooser extends ByteToMessageDecoder { - - private static final InternalLogger logger = InternalLoggerFactory.getInstance(SpdyOrHttpChooser.class); - - // TODO: Replace with generic NPN handler - - public enum SelectedProtocol { - SPDY_3_1("spdy/3.1"), - HTTP_1_1("http/1.1"), - HTTP_1_0("http/1.0"); - - private final String name; - - SelectedProtocol(String defaultName) { - name = defaultName; - } - - public String protocolName() { - return name; - } - - /** - * Get an instance of this enum based on the protocol name returned by the NPN server provider - * - * @param name the protocol name - * @return the selected protocol or {@code null} if there is no match - */ - public static SelectedProtocol protocol(String name) { - for (SelectedProtocol protocol : SelectedProtocol.values()) { - if (protocol.protocolName().equals(name)) { - return protocol; - } - } - return null; - } - } - - protected SpdyOrHttpChooser() { } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (configurePipeline(ctx)) { - // When we reached here we can remove this handler as its now clear - // what protocol we want to use - // from this point on. This will also take care of forward all - // messages. - ctx.pipeline().remove(this); - } - } - - private boolean configurePipeline(ChannelHandlerContext ctx) { - // Get the SslHandler from the ChannelPipeline so we can obtain the - // SslEngine from it. - SslHandler handler = ctx.pipeline().get(SslHandler.class); - if (handler == null) { - // SslHandler is needed by SPDY by design. - throw new IllegalStateException("cannot find a SslHandler in the pipeline (required for SPDY)"); - } - - if (!handler.handshakeFuture().isDone()) { - return false; - } - - SelectedProtocol protocol; - try { - protocol = selectProtocol(handler); - } catch (Exception e) { - throw new IllegalStateException("failed to get the selected protocol", e); - } - - if (protocol == null) { - throw new IllegalStateException("unknown protocol"); - } - - switch (protocol) { - case SPDY_3_1: - try { - configureSpdy(ctx, SpdyVersion.SPDY_3_1); - } catch (Exception e) { - throw new IllegalStateException("failed to configure a SPDY pipeline", e); - } - break; - case HTTP_1_0: - case HTTP_1_1: - try { - configureHttp1(ctx); - } catch (Exception e) { - throw new IllegalStateException("failed to configure a HTTP/1 pipeline", e); - } - break; - } - return true; - } - - /** - * Returns the {@link SelectedProtocol} for the current SSL session. By default, this method returns the first - * known protocol. - * - * @return the selected application-level protocol, or {@code null} if the application-level protocol name of - * the specified {@code sslHandler} is neither {@code "http/1.1"}, {@code "http/1.0"} nor {@code "spdy/3.1"} - */ - protected SelectedProtocol selectProtocol(SslHandler sslHandler) throws Exception { - final String appProto = sslHandler.applicationProtocol(); - return appProto != null? SelectedProtocol.protocol(appProto) : SelectedProtocol.HTTP_1_1; - } - - /** - * Configures the {@link Channel} of the specified {@code ctx} for HTTP/2. - *

- * A typical implementation of this method will look like the following: - *

-     * {@link ChannelPipeline} p = ctx.pipeline();
-     * p.addLast(new {@link SpdyFrameCodec}(version));
-     * p.addLast(new {@link SpdySessionHandler}(version, true));
-     * p.addLast(new {@link SpdyHttpEncoder}(version));
-     * p.addLast(new {@link SpdyHttpDecoder}(version, maxSpdyContentLength));
-     * p.addLast(new {@link SpdyHttpResponseStreamIdHandler}());
-     * p.addLast(new YourHttpRequestHandler());
-     * 
- *

- */ - protected abstract void configureSpdy(ChannelHandlerContext ctx, SpdyVersion version) throws Exception; - - /** - * Configures the {@link Channel} of the specified {@code ctx} for HTTP/1. - *

- * A typical implementation of this method will look like the following: - *

-     * {@link ChannelPipeline} p = ctx.pipeline();
-     * p.addLast(new {@link HttpServerCodec}());
-     * p.addLast(new YourHttpRequestHandler());
-     * 
- *

- */ - protected abstract void configureHttp1(ChannelHandlerContext ctx) throws Exception; - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - logger.warn("{} Failed to select the application-level protocol:", ctx.channel(), cause); - ctx.close(); - } -} diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java index 03daf07bc5..27c69846ce 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java @@ -24,6 +24,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultChannelPromise; +import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.util.concurrent.EventExecutor; /** @@ -38,7 +39,7 @@ public final class Http2CodecUtil { public static final int HTTP_UPGRADE_STREAM_ID = 1; public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings"; public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c"; - public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2"; + public static final String TLS_UPGRADE_PROTOCOL_NAME = ApplicationProtocolNames.HTTP_2; public static final int PING_FRAME_PAYLOAD_LENGTH = 8; public static final short MAX_UNSIGNED_BYTE = 0xFF; diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java deleted file mode 100644 index 24a64fbe4d..0000000000 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2OrHttpChooser.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.handler.codec.http2; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPipeline; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.http.HttpServerCodec; -import io.netty.handler.ssl.SslHandler; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; - -import java.util.List; - -import static io.netty.handler.codec.http2.Http2CodecUtil.*; - -/** - * {@link ChannelHandler} which is responsible to setup the - * {@link ChannelPipeline} either for HTTP or HTTP2. This offers an easy way for - * users to support both at the same time while not care to much about the low-level details. - */ -public abstract class Http2OrHttpChooser extends ByteToMessageDecoder { - - private static final InternalLogger logger = InternalLoggerFactory.getInstance(Http2OrHttpChooser.class); - - public enum SelectedProtocol { - /** Must be updated to match the HTTP/2 draft number. */ - HTTP_2(TLS_UPGRADE_PROTOCOL_NAME), - HTTP_1_1("http/1.1"), - HTTP_1_0("http/1.0"); - - private final String name; - - SelectedProtocol(String defaultName) { - name = defaultName; - } - - public String protocolName() { - return name; - } - - /** - * Get an instance of this enum based on the protocol name returned by the ALPN server provider - * - * @param name the protocol name - * @return the selected protocol or {@code null} if there is no match - */ - public static SelectedProtocol protocol(String name) { - for (SelectedProtocol protocol : SelectedProtocol.values()) { - if (protocol.protocolName().equals(name)) { - return protocol; - } - } - return null; - } - } - - protected Http2OrHttpChooser() { } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (configurePipeline(ctx)) { - // When we reached here we can remove this handler as its now clear - // what protocol we want to use - // from this point on. This will also take care of forward all - // messages. - ctx.pipeline().remove(this); - } - } - - private boolean configurePipeline(ChannelHandlerContext ctx) { - // Get the SslHandler from the ChannelPipeline so we can obtain the - // SslEngine from it. - SslHandler handler = ctx.pipeline().get(SslHandler.class); - if (handler == null) { - // HTTP/2 is negotiated through SSL. - throw new IllegalStateException("cannot find a SslHandler in the pipeline (required for HTTP/2)"); - } - - if (!handler.handshakeFuture().isDone()) { - return false; - } - - SelectedProtocol protocol; - try { - protocol = selectProtocol(handler); - } catch (Exception e) { - throw new IllegalStateException("failed to get the selected protocol", e); - } - - if (protocol == null) { - throw new IllegalStateException("unknown protocol"); - } - - switch (protocol) { - case HTTP_2: - try { - configureHttp2(ctx); - } catch (Exception e) { - throw new IllegalStateException("failed to configure a HTTP/2 pipeline", e); - } - break; - case HTTP_1_0: - case HTTP_1_1: - try { - configureHttp1(ctx); - } catch (Exception e) { - throw new IllegalStateException("failed to configure a HTTP/1 pipeline", e); - } - break; - } - return true; - } - - /** - * Returns the {@link SelectedProtocol} for the current SSL session. By default, this method returns the first - * known protocol. - * - * @return the selected application-level protocol, or {@code null} if the application-level protocol name of - * the specified {@code sslHandler} is neither {@code "http/1.1"}, {@code "http/1.0"} nor {@code "h2"} - */ - protected SelectedProtocol selectProtocol(SslHandler sslHandler) throws Exception { - final String appProto = sslHandler.applicationProtocol(); - return appProto != null? SelectedProtocol.protocol(appProto) : SelectedProtocol.HTTP_1_1; - } - - /** - * Configures the {@link Channel} of the specified {@code ctx} for HTTP/2. - *

- * A typical implementation of this method will add a new {@link Http2ConnectionHandler} implementation - * to the pipeline. - *

- */ - protected abstract void configureHttp2(ChannelHandlerContext ctx) throws Exception; - - /** - * Configures the {@link Channel} of the specified {@code ctx} for HTTP/1. - *

- * A typical implementation of this method will look like the following: - *

-     * {@link ChannelPipeline} p = ctx.pipeline();
-     * p.addLast(new {@link HttpServerCodec}());
-     * p.addLast(new YourHttpRequestHandler());
-     * 
- *

- */ - protected abstract void configureHttp1(ChannelHandlerContext ctx) throws Exception; - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - logger.warn("{} Failed to select the application-level protocol:", ctx.channel(), cause); - ctx.close(); - } -} diff --git a/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java index 35c9dd5158..1e4c08af9a 100644 --- a/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java +++ b/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java @@ -25,12 +25,12 @@ import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; -import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol; import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; @@ -77,8 +77,8 @@ public final class Http2Client { SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. SelectedListenerFailureBehavior.ACCEPT, - SelectedProtocol.HTTP_2.protocolName(), - SelectedProtocol.HTTP_1_1.protocolName())) + ApplicationProtocolNames.HTTP_2, + ApplicationProtocolNames.HTTP_1_1)) .build(); } else { sslCtx = null; diff --git a/example/src/main/java/io/netty/example/http2/helloworld/server/Http2OrHttpHandler.java b/example/src/main/java/io/netty/example/http2/helloworld/server/Http2OrHttpHandler.java index 05d61d22fa..7976fe9317 100644 --- a/example/src/main/java/io/netty/example/http2/helloworld/server/Http2OrHttpHandler.java +++ b/example/src/main/java/io/netty/example/http2/helloworld/server/Http2OrHttpHandler.java @@ -17,25 +17,35 @@ package io.netty.example.http2.helloworld.server; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; -import io.netty.handler.codec.http2.Http2OrHttpChooser; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; /** * Negotiates with the browser if HTTP2 or HTTP is going to be used. Once decided, the Netty * pipeline is setup with the correct handlers for the selected protocol. */ -public class Http2OrHttpHandler extends Http2OrHttpChooser { +public class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler { private static final int MAX_CONTENT_LENGTH = 1024 * 100; - @Override - protected void configureHttp2(ChannelHandlerContext ctx) throws Exception { - ctx.pipeline().addLast(new HelloWorldHttp2Handler()); + protected Http2OrHttpHandler() { + super(ApplicationProtocolNames.HTTP_1_1); } @Override - protected void configureHttp1(ChannelHandlerContext ctx) throws Exception { - ctx.pipeline().addLast(new HttpServerCodec(), - new HttpObjectAggregator(MAX_CONTENT_LENGTH), - new HelloWorldHttp1Handler("ALPN Negotiation")); + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception { + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + ctx.pipeline().addLast(new HelloWorldHttp2Handler()); + return; + } + + if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { + ctx.pipeline().addLast(new HttpServerCodec(), + new HttpObjectAggregator(MAX_CONTENT_LENGTH), + new HelloWorldHttp1Handler("ALPN Negotiation")); + return; + } + + throw new IllegalStateException("unknown protocol: " + protocol); } } diff --git a/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java index 26a10ccb7a..b99b3755a3 100644 --- a/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java +++ b/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java @@ -22,7 +22,6 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol; import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; @@ -30,6 +29,7 @@ import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; @@ -64,8 +64,8 @@ public final class Http2Server { SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. SelectedListenerFailureBehavior.ACCEPT, - SelectedProtocol.HTTP_2.protocolName(), - SelectedProtocol.HTTP_1_1.protocolName())) + ApplicationProtocolNames.HTTP_2, + ApplicationProtocolNames.HTTP_1_1)) .build(); } else { sslCtx = null; diff --git a/example/src/main/java/io/netty/example/http2/tiles/Http2OrHttpHandler.java b/example/src/main/java/io/netty/example/http2/tiles/Http2OrHttpHandler.java index 73b2c7563b..0d1985852d 100644 --- a/example/src/main/java/io/netty/example/http2/tiles/Http2OrHttpHandler.java +++ b/example/src/main/java/io/netty/example/http2/tiles/Http2OrHttpHandler.java @@ -22,36 +22,55 @@ import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http2.DefaultHttp2Connection; import io.netty.handler.codec.http2.DefaultHttp2FrameReader; import io.netty.handler.codec.http2.DefaultHttp2FrameWriter; -import io.netty.handler.codec.http2.Http2OrHttpChooser; import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler; import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; /** * Used during protocol negotiation, the main function of this handler is to * return the HTTP/1.1 or HTTP/2 handler once the protocol has been negotiated. */ -public class Http2OrHttpHandler extends Http2OrHttpChooser { +public class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler { private static final int MAX_CONTENT_LENGTH = 1024 * 100; + protected Http2OrHttpHandler() { + super(ApplicationProtocolNames.HTTP_1_1); + } + @Override - protected void configureHttp2(ChannelHandlerContext ctx) { + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception { + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + configureHttp2(ctx); + return; + } + + if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { + configureHttp1(ctx); + return; + } + + throw new IllegalStateException("unknown protocol: " + protocol); + } + + private static void configureHttp2(ChannelHandlerContext ctx) { DefaultHttp2Connection connection = new DefaultHttp2Connection(true); DefaultHttp2FrameWriter writer = new DefaultHttp2FrameWriter(); DefaultHttp2FrameReader reader = new DefaultHttp2FrameReader(); - InboundHttp2ToHttpAdapter listener = new InboundHttp2ToHttpAdapter.Builder(connection).propagateSettings(true) - .validateHttpHeaders(false).maxContentLength(MAX_CONTENT_LENGTH).build(); + InboundHttp2ToHttpAdapter listener = new InboundHttp2ToHttpAdapter.Builder(connection) + .propagateSettings(true).validateHttpHeaders(false).maxContentLength(MAX_CONTENT_LENGTH).build(); - ctx.pipeline().addLast("httpToHttp2", new HttpToHttp2ConnectionHandler(connection, + ctx.pipeline().addLast(new HttpToHttp2ConnectionHandler( + connection, // Loggers can be activated for debugging purposes // new Http2InboundFrameLogger(reader, TilesHttp2ToHttpHandler.logger), // new Http2OutboundFrameLogger(writer, TilesHttp2ToHttpHandler.logger) reader, writer, listener)); - ctx.pipeline().addLast("fullHttpRequestHandler", new Http2RequestHandler()); + ctx.pipeline().addLast(new Http2RequestHandler()); } - @Override - protected void configureHttp1(ChannelHandlerContext ctx) throws Exception { + private static void configureHttp1(ChannelHandlerContext ctx) throws Exception { ctx.pipeline().addLast(new HttpServerCodec(), new HttpObjectAggregator(MAX_CONTENT_LENGTH), new FallbackRequestHandler()); diff --git a/example/src/main/java/io/netty/example/http2/tiles/Http2Server.java b/example/src/main/java/io/netty/example/http2/tiles/Http2Server.java index b870a64ead..2a50c5a248 100644 --- a/example/src/main/java/io/netty/example/http2/tiles/Http2Server.java +++ b/example/src/main/java/io/netty/example/http2/tiles/Http2Server.java @@ -16,8 +16,6 @@ package io.netty.example.http2.tiles; -import static io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol.HTTP_1_1; -import static io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol.HTTP_2; import static io.netty.handler.codec.http2.Http2SecurityUtil.CIPHERS; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; @@ -31,6 +29,7 @@ import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SupportedCipherSuiteFilter; @@ -70,17 +69,19 @@ public class Http2Server { return ch.closeFuture(); } - private SslContext configureTLS() throws CertificateException, SSLException { + private static SslContext configureTLS() throws CertificateException, SSLException { SelfSignedCertificate ssc = new SelfSignedCertificate(); - ApplicationProtocolConfig apn = new ApplicationProtocolConfig(Protocol.ALPN, - // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. - SelectorFailureBehavior.NO_ADVERTISE, - // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. - SelectedListenerFailureBehavior.ACCEPT, - HTTP_2.protocolName(), HTTP_1_1.protocolName()); - final SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey(), null) + ApplicationProtocolConfig apn = new ApplicationProtocolConfig( + Protocol.ALPN, + // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. + SelectorFailureBehavior.NO_ADVERTISE, + // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. + SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2, + ApplicationProtocolNames.HTTP_1_1); + + return SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey(), null) .ciphers(CIPHERS, SupportedCipherSuiteFilter.INSTANCE) .applicationProtocolConfig(apn).build(); - return sslCtx; } } diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java index 12062fef9e..22a2acd592 100644 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java +++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java @@ -27,11 +27,11 @@ import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; @@ -64,8 +64,8 @@ public final class SpdyClient { SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. SelectedListenerFailureBehavior.ACCEPT, - SelectedProtocol.SPDY_3_1.protocolName(), - SelectedProtocol.HTTP_1_1.protocolName())) + ApplicationProtocolNames.SPDY_3_1, + ApplicationProtocolNames.HTTP_1_1)) .build(); HttpResponseClientHandler httpResponseHandler = new HttpResponseClientHandler(); diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java b/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java index 19490a2cd6..bed9bd24da 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java @@ -23,20 +23,39 @@ import io.netty.handler.codec.spdy.SpdyFrameCodec; import io.netty.handler.codec.spdy.SpdyHttpDecoder; import io.netty.handler.codec.spdy.SpdyHttpEncoder; import io.netty.handler.codec.spdy.SpdyHttpResponseStreamIdHandler; -import io.netty.handler.codec.spdy.SpdyOrHttpChooser; import io.netty.handler.codec.spdy.SpdySessionHandler; import io.netty.handler.codec.spdy.SpdyVersion; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; /** * Negotiates with the browser if SPDY or HTTP is going to be used. Once decided, the Netty pipeline is setup with * the correct handlers for the selected protocol. */ -public class SpdyOrHttpHandler extends SpdyOrHttpChooser { +public class SpdyOrHttpHandler extends ApplicationProtocolNegotiationHandler { private static final int MAX_CONTENT_LENGTH = 1024 * 100; + protected SpdyOrHttpHandler() { + super(ApplicationProtocolNames.HTTP_1_1); + } + @Override - protected void configureSpdy(ChannelHandlerContext ctx, SpdyVersion version) throws Exception { + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception { + if (ApplicationProtocolNames.SPDY_3_1.equals(protocol)) { + configureSpdy(ctx, SpdyVersion.SPDY_3_1); + return; + } + + if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { + configureHttp1(ctx); + return; + } + + throw new IllegalStateException("unknown protocol: " + protocol); + } + + private static void configureSpdy(ChannelHandlerContext ctx, SpdyVersion version) throws Exception { ChannelPipeline p = ctx.pipeline(); p.addLast(new SpdyFrameCodec(version)); p.addLast(new SpdySessionHandler(version, true)); @@ -46,8 +65,7 @@ public class SpdyOrHttpHandler extends SpdyOrHttpChooser { p.addLast(new SpdyServerHandler()); } - @Override - protected void configureHttp1(ChannelHandlerContext ctx) throws Exception { + private static void configureHttp1(ChannelHandlerContext ctx) throws Exception { ChannelPipeline p = ctx.pipeline(); p.addLast(new HttpServerCodec()); p.addLast(new HttpObjectAggregator(MAX_CONTENT_LENGTH)); diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java index 67d3cf24e8..827988ddec 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java @@ -21,13 +21,13 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.SelfSignedCertificate; @@ -64,8 +64,8 @@ public final class SpdyServer { SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. SelectedListenerFailureBehavior.ACCEPT, - SelectedProtocol.SPDY_3_1.protocolName(), - SelectedProtocol.HTTP_1_1.protocolName())) + ApplicationProtocolNames.SPDY_3_1, + ApplicationProtocolNames.HTTP_1_1)) .build(); // Configure the server. diff --git a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolAccessor.java b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolAccessor.java index 812762db3c..8835bc810a 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolAccessor.java +++ b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolAccessor.java @@ -20,5 +20,11 @@ package io.netty.handler.ssl; * Provides a way to get the application-level protocol name from ALPN or NPN. */ interface ApplicationProtocolAccessor { + /** + * Returns the name of the negotiated application-level protocol. + * + * @return the application-level protocol name or + * {@code null} if the negotiation failed or the client does not have ALPN/NPN extension + */ String getApplicationProtocol(); } diff --git a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNames.java b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNames.java new file mode 100644 index 0000000000..b17fd69804 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNames.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +/** + * Provides a set of protocol names used in ALPN and NPN. + * + * @see RFC7540 (HTTP/2) + * @see RFC7301 (TLS ALPN Extension) + * @see TLS NPN Extension Draft + */ +public final class ApplicationProtocolNames { + + /** + * {@code "h2"}: HTTP version 2 + */ + public static final String HTTP_2 = "h2"; + + /** + * {@code "http/1.1"}: HTTP version 1.1 + */ + public static final String HTTP_1_1 = "http/1.1"; + + /** + * {@code "spdy/3.1"}: SPDY version 3.1 + */ + public static final String SPDY_3_1 = "spdy/3.1"; + + /** + * {@code "spdy/3"}: SPDY version 3 + */ + public static final String SPDY_3 = "spdy/3"; + + /** + * {@code "spdy/2"}: SPDY version 2 + */ + public static final String SPDY_2 = "spdy/2"; + + /** + * {@code "spdy/1"}: SPDY version 1 + */ + public static final String SPDY_1 = "spdy/1"; + + private ApplicationProtocolNames() { } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java new file mode 100644 index 0000000000..48a632706d --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java @@ -0,0 +1,125 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.ssl; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.util.internal.ObjectUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +/** + * Configures a {@link ChannelPipeline} depending on the application-level protocol negotiation result of + * {@link SslHandler}. For example, you could configure your HTTP pipeline depending on the result of ALPN: + *
+ * public class MyInitializer extends {@link ChannelInitializer}<{@link Channel}> {
+ *     private final {@link SslContext} sslCtx;
+ *
+ *     public MyInitializer({@link SslContext} sslCtx) {
+ *         this.sslCtx = sslCtx;
+ *     }
+ *
+ *     protected void initChannel({@link Channel} ch) {
+ *         {@link ChannelPipeline} p = ch.pipeline();
+ *         p.addLast(sslCtx.newHandler(...)); // Adds {@link SslHandler}
+ *         p.addLast(new MyNegotiationHandler());
+ *     }
+ * }
+ *
+ * public class MyNegotiationHandler extends {@link ApplicationProtocolNegotiationHandler} {
+ *     public MyNegotiationHandler() {
+ *         super({@link ApplicationProtocolNames}.HTTP_1_1);
+ *     }
+ *
+ *     protected void configurePipeline({@link ChannelHandlerContext} ctx, String protocol) {
+ *         if ({@link ApplicationProtocolNames}.HTTP_2.equals(protocol) {
+ *             configureHttp2(ctx);
+ *         } else if ({@link ApplicationProtocolNames}.HTTP_1_1.equals(protocol)) {
+ *             configureHttp1(ctx);
+ *         } else {
+ *             throw new IllegalStateException("unknown protocol: " + protocol);
+ *         }
+ *     }
+ * }
+ * 
+ */ +public abstract class ApplicationProtocolNegotiationHandler extends ChannelInboundHandlerAdapter { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(ApplicationProtocolNegotiationHandler.class); + + private final String fallbackProtocol; + private SslHandler sslHandler; + + /** + * Creates a new instance with the specified fallback protocol name. + * + * @param fallbackProtocol the name of the protocol to use when + * ALPN/NPN negotiation fails or the client does not support ALPN/NPN + */ + protected ApplicationProtocolNegotiationHandler(String fallbackProtocol) { + this.fallbackProtocol = ObjectUtil.checkNotNull(fallbackProtocol, "fallbackProtocol"); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + // FIXME: There is no way to tell if the SSL handler is placed before the negotiation handler. + final SslHandler sslHandler = ctx.pipeline().get(SslHandler.class); + if (sslHandler == null) { + throw new IllegalStateException( + "cannot find a SslHandler in the pipeline (required for application-level protocol negotiation)"); + } + + this.sslHandler = sslHandler; + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof SslHandshakeCompletionEvent) { + ctx.pipeline().remove(this); + + SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt; + if (handshakeEvent.isSuccess()) { + String protocol = sslHandler.applicationProtocol(); + configurePipeline(ctx, protocol != null? protocol : fallbackProtocol); + } else { + logger.warn("{} TLS handshake failed:", ctx.channel(), handshakeEvent.cause()); + ctx.close(); + } + } + + ctx.fireUserEventTriggered(evt); + } + + /** + * Invoked on successful initial SSL/TLS handshake. Implement this method to configure your pipeline + * for the negotiated application-level protocol. + * + * @param protocol the name of the negotiated application-level protocol, or + * the fallback protocol name specified in the constructor call if negotiation failed or the client + * isn't aware of ALPN/NPN extension + */ + protected abstract void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception; + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.warn("{} Failed to select the application-level protocol:", ctx.channel(), cause); + ctx.close(); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolUtil.java b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolUtil.java index 03a077ec69..0581e9b2c2 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolUtil.java +++ b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolUtil.java @@ -22,7 +22,6 @@ import java.util.List; * Utility class for application protocol common operations. */ final class ApplicationProtocolUtil { - private static final int DEFAULT_LIST_SIZE = 2; private ApplicationProtocolUtil() { diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandshakeCompletionEvent.java b/handler/src/main/java/io/netty/handler/ssl/SslHandshakeCompletionEvent.java index b2a1de061f..a81e087e23 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandshakeCompletionEvent.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandshakeCompletionEvent.java @@ -61,11 +61,7 @@ public final class SslHandshakeCompletionEvent { @Override public String toString() { - Throwable cause = cause(); - if (cause == null) { - return "SslHandshakeCompletionEvent(SUCCESS)"; - } else { - return "SslHandshakeCompletionEvent(" + cause + ')'; - } + final Throwable cause = cause(); + return cause == null? "SslHandshakeCompletionEvent(SUCCESS)" : "SslHandshakeCompletionEvent(" + cause + ')'; } }