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 + ')';
}
}