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
- Deprecate SpdyOrHttpChooser

Result:

- Less code duplication
- A user can perform dynamic pipeline configuration that follows ALPN/NPN for any protocols.
This commit is contained in:
Trustin Lee 2015-06-01 16:05:00 +09:00
parent 67e02dad0a
commit 263e6979a6
9 changed files with 224 additions and 24 deletions

View File

@ -17,11 +17,12 @@ package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
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.ApplicationProtocolNegotiationHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -29,16 +30,12 @@ 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.
* @deprecated Use {@link ApplicationProtocolNegotiationHandler} instead.
*/
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"),

View File

@ -26,11 +26,11 @@ import io.netty.handler.codec.http.HttpHeaders;
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;
@ -63,8 +63,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();

View File

@ -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));

View File

@ -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.

View File

@ -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();
}

View File

@ -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 <a href="https://tools.ietf.org/html/rfc7540#section-11.1">RFC7540 (HTTP/2)</a>
* @see <a href="https://tools.ietf.org/html/rfc7301#section-6">RFC7301 (TLS ALPN Extension)</a>
* @see <a href="https://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#section-7">TLS NPN Extension Draft</a>
*/
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() { }
}

View File

@ -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:
* <pre>
* public class MyInitializer extends {@link ChannelInitializer}&lt;{@link Channel}&gt; {
* 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);
* }
* }
* }
* </pre>
*/
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();
}
}

View File

@ -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() {

View File

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