Improve the API design of Http2OrHttpChooser and SpdyOrHttpChooser
Related: #3641 and #3813 Motivation: When setting up an HTTP/1 or HTTP/2 (or SPDY) pipeline, a user usually ends up with adding arbitrary set of handlers. Http2OrHttpChooser and SpdyOrHttpChooser have two abstract methods (create*Handler()) that expect a user to return a single handler, and also have add*Handlers() methods that add the handler returned by create*Handler() to the pipeline as well as the pre-defined set of handlers. The problem is, some users (read: I) don't need all of them or the user wants to add more than one handler. For example, take a look at io.netty.example.http2.tiles.Http2OrHttpHandler, which works around this issue by overriding addHttp2Handlers() and making createHttp2RequestHandler() a no-op. Modifications: - Replace add*Handlers() and create*Handler() with configure*() - Rename getProtocol() to selectProtocol() to make what it does clear - Provide the default implementation of selectProtocol() - Remove SelectedProtocol.UNKNOWN and use null instead, because 'UNKNOWN' is not a protocol - Proper exception handling in the *OrHttpChooser so that the exception is logged and the connection is closed when failed to select a protocol - Make SpdyClient example always use SSL. It was always using SSL anyway. - Implement SslHandshakeCompletionEvent.toString() for debuggability - Remove an orphaned class: JettyNpnSslSession - Add SslHandler.applicationProtocol() to get the name of the application protocol - SSLSession.getProtocol() now returns transport-layer protocol name only, so that it conforms to its contract. Result: - *OrHttpChooser have better API. - *OrHttpChooser handle protocol selection failure properly. - SSLSession.getProtocol() now conforms to its contract. - SpdyClient example works with SpdyServer example out of the box
This commit is contained in:
parent
e19e1e29d8
commit
2e231283cf
@ -16,17 +16,16 @@
|
||||
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.ChannelPipeline;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -35,13 +34,14 @@ import java.util.List;
|
||||
*/
|
||||
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"),
|
||||
UNKNOWN("Unknown");
|
||||
HTTP_1_0("http/1.0");
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -56,9 +56,8 @@ public abstract class SpdyOrHttpChooser extends ByteToMessageDecoder {
|
||||
/**
|
||||
* Get an instance of this enum based on the protocol name returned by the NPN server provider
|
||||
*
|
||||
* @param name
|
||||
* the protocol name
|
||||
* @return the SelectedProtocol instance
|
||||
* @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()) {
|
||||
@ -66,36 +65,15 @@ public abstract class SpdyOrHttpChooser extends ByteToMessageDecoder {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final int maxSpdyContentLength;
|
||||
private final int maxHttpContentLength;
|
||||
|
||||
protected SpdyOrHttpChooser(int maxSpdyContentLength, int maxHttpContentLength) {
|
||||
this.maxSpdyContentLength = maxSpdyContentLength;
|
||||
this.maxHttpContentLength = maxHttpContentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SelectedProtocol} for the {@link SSLEngine}. If its not known yet implementations MUST return
|
||||
* {@link SelectedProtocol#UNKNOWN}.
|
||||
*
|
||||
*/
|
||||
protected SelectedProtocol getProtocol(SSLEngine engine) {
|
||||
String[] protocol = StringUtil.split(engine.getSession().getProtocol(), ':');
|
||||
if (protocol.length < 2) {
|
||||
// Use HTTP/1.1 as default
|
||||
return SelectedProtocol.HTTP_1_1;
|
||||
}
|
||||
SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]);
|
||||
return selectedProtocol;
|
||||
}
|
||||
protected SpdyOrHttpChooser() { }
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (initPipeline(ctx)) {
|
||||
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
|
||||
@ -104,71 +82,95 @@ public abstract class SpdyOrHttpChooser extends ByteToMessageDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initPipeline(ChannelHandlerContext ctx) {
|
||||
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("SslHandler is needed for SPDY");
|
||||
throw new IllegalStateException("cannot find a SslHandler in the pipeline (required for SPDY)");
|
||||
}
|
||||
|
||||
SelectedProtocol protocol = getProtocol(handler.engine());
|
||||
switch (protocol) {
|
||||
case UNKNOWN:
|
||||
// Not done with choosing the protocol, so just return here for now,
|
||||
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:
|
||||
addSpdyHandlers(ctx, SpdyVersion.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:
|
||||
addHttpHandlers(ctx);
|
||||
try {
|
||||
configureHttp1(ctx);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("failed to configure a HTTP/1 pipeline", e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown SelectedProtocol");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all {@link ChannelHandler}'s that are needed for SPDY with the given version.
|
||||
*/
|
||||
protected void addSpdyHandlers(ChannelHandlerContext ctx, SpdyVersion version) {
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(version));
|
||||
pipeline.addLast("spdySessionHandler", new SpdySessionHandler(version, true));
|
||||
pipeline.addLast("spdyHttpEncoder", new SpdyHttpEncoder(version));
|
||||
pipeline.addLast("spdyHttpDecoder", new SpdyHttpDecoder(version, maxSpdyContentLength));
|
||||
pipeline.addLast("spdyStreamIdHandler", new SpdyHttpResponseStreamIdHandler());
|
||||
pipeline.addLast("httpRequestHandler", createHttpRequestHandlerForSpdy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all {@link ChannelHandler}'s that are needed for HTTP.
|
||||
*/
|
||||
protected void addHttpHandlers(ChannelHandlerContext ctx) {
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder());
|
||||
pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder());
|
||||
pipeline.addLast("httpChunkAggregator", new HttpObjectAggregator(maxHttpContentLength));
|
||||
pipeline.addLast("httpRequestHandler", createHttpRequestHandlerForHttp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link ChannelHandler} that is responsible for handling the http requests when the
|
||||
* {@link SelectedProtocol} was {@link SelectedProtocol#HTTP_1_0} or {@link SelectedProtocol#HTTP_1_1}
|
||||
*/
|
||||
protected abstract ChannelHandler createHttpRequestHandlerForHttp();
|
||||
|
||||
/**
|
||||
* Create the {@link ChannelHandler} that is responsible for handling the http responses when the
|
||||
* when the {@link SelectedProtocol} was {@link SelectedProtocol#SPDY_3_1}.
|
||||
* Returns the {@link SelectedProtocol} for the current SSL session. By default, this method returns the first
|
||||
* known protocol.
|
||||
*
|
||||
* By default this getMethod will just delecate to {@link #createHttpRequestHandlerForHttp()}, but sub-classes may
|
||||
* override this to change the behaviour.
|
||||
* @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 ChannelHandler createHttpRequestHandlerForSpdy() {
|
||||
return createHttpRequestHandlerForHttp();
|
||||
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.
|
||||
* <p>
|
||||
* A typical implementation of this method will look like the following:
|
||||
* <pre>
|
||||
* {@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, <i>maxSpdyContentLength</i>));
|
||||
* p.addLast(new {@link SpdyHttpResponseStreamIdHandler}());
|
||||
* p.addLast(new <i>YourHttpRequestHandler</i>());
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
protected abstract void configureSpdy(ChannelHandlerContext ctx, SpdyVersion version) throws Exception;
|
||||
|
||||
/**
|
||||
* Configures the {@link Channel} of the specified {@code ctx} for HTTP/1.
|
||||
* <p>
|
||||
* A typical implementation of this method will look like the following:
|
||||
* <pre>
|
||||
* {@link ChannelPipeline} p = ctx.pipeline();
|
||||
* p.addLast(new {@link HttpServerCodec}());
|
||||
* p.addLast(new <i>YourHttpRequestHandler</i>());
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -15,34 +15,35 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.TLS_UPGRADE_PROTOCOL_NAME;
|
||||
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.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
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 javax.net.ssl.SSLEngine;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
||||
|
||||
/**
|
||||
* {@link io.netty.channel.ChannelHandler} which is responsible to setup the
|
||||
* {@link io.netty.channel.ChannelPipeline} either for HTTP or HTTP2. This offers an easy way for
|
||||
* {@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"),
|
||||
UNKNOWN("Unknown");
|
||||
HTTP_1_0("http/1.0");
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -55,11 +56,10 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of this enum based on the protocol name returned by the NPN server provider
|
||||
* Get an instance of this enum based on the protocol name returned by the ALPN server provider
|
||||
*
|
||||
* @param name
|
||||
* the protocol name
|
||||
* @return the SelectedProtocol instance
|
||||
* @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()) {
|
||||
@ -67,25 +67,15 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final int maxHttpContentLength;
|
||||
|
||||
protected Http2OrHttpChooser(int maxHttpContentLength) {
|
||||
this.maxHttpContentLength = maxHttpContentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SelectedProtocol} for the {@link javax.net.ssl.SSLEngine}. If its not known
|
||||
* yet implementations MUST return {@link SelectedProtocol#UNKNOWN}.
|
||||
*/
|
||||
protected abstract SelectedProtocol getProtocol(SSLEngine engine);
|
||||
protected Http2OrHttpChooser() { }
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (initPipeline(ctx)) {
|
||||
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
|
||||
@ -94,62 +84,87 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initPipeline(ChannelHandlerContext ctx) {
|
||||
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) {
|
||||
// HTTP2 is negotiated through SSL.
|
||||
throw new IllegalStateException("SslHandler is needed for HTTP2");
|
||||
// HTTP/2 is negotiated through SSL.
|
||||
throw new IllegalStateException("cannot find a SslHandler in the pipeline (required for HTTP/2)");
|
||||
}
|
||||
|
||||
SelectedProtocol protocol = getProtocol(handler.engine());
|
||||
switch (protocol) {
|
||||
case UNKNOWN:
|
||||
// Not done with choosing the protocol, so just return here for now,
|
||||
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:
|
||||
addHttp2Handlers(ctx);
|
||||
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:
|
||||
addHttpHandlers(ctx);
|
||||
try {
|
||||
configureHttp1(ctx);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("failed to configure a HTTP/1 pipeline", e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown SelectedProtocol");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all {@link io.netty.channel.ChannelHandler}'s that are needed for HTTP_2.
|
||||
* 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 void addHttp2Handlers(ChannelHandlerContext ctx) {
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
pipeline.addLast("http2ConnectionHandler", createHttp2RequestHandler());
|
||||
protected SelectedProtocol selectProtocol(SslHandler sslHandler) throws Exception {
|
||||
final String appProto = sslHandler.applicationProtocol();
|
||||
return appProto != null? SelectedProtocol.protocol(appProto) : SelectedProtocol.HTTP_1_1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all {@link io.netty.channel.ChannelHandler}'s that are needed for HTTP.
|
||||
* Configures the {@link Channel} of the specified {@code ctx} for HTTP/2.
|
||||
* <p>
|
||||
* A typical implementation of this method will add a new {@link Http2ConnectionHandler} implementation
|
||||
* to the pipeline.
|
||||
* </p>
|
||||
*/
|
||||
protected void addHttpHandlers(ChannelHandlerContext ctx) {
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder());
|
||||
pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder());
|
||||
pipeline.addLast("httpChunkAggregator", new HttpObjectAggregator(maxHttpContentLength));
|
||||
pipeline.addLast("httpRequestHandler", createHttp1RequestHandler());
|
||||
}
|
||||
protected abstract void configureHttp2(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Create the {@link io.netty.channel.ChannelHandler} that is responsible for handling the http
|
||||
* requests when the {@link SelectedProtocol} was {@link SelectedProtocol#HTTP_1_0} or
|
||||
* {@link SelectedProtocol#HTTP_1_1}
|
||||
* Configures the {@link Channel} of the specified {@code ctx} for HTTP/1.
|
||||
* <p>
|
||||
* A typical implementation of this method will look like the following:
|
||||
* <pre>
|
||||
* {@link ChannelPipeline} p = ctx.pipeline();
|
||||
* p.addLast(new {@link HttpServerCodec}());
|
||||
* p.addLast(new <i>YourHttpRequestHandler</i>());
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
protected abstract ChannelHandler createHttp1RequestHandler();
|
||||
protected abstract void configureHttp1(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Create the {@link ChannelHandler} that is responsible for handling the http responses
|
||||
* when the when the {@link SelectedProtocol} was {@link SelectedProtocol#HTTP_2}.
|
||||
*/
|
||||
protected abstract Http2ConnectionHandler createHttp2RequestHandler();
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
logger.warn("{} Failed to select the application-level protocol:", ctx.channel(), cause);
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
@ -14,45 +14,28 @@
|
||||
*/
|
||||
package io.netty.example.http2.helloworld.server;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.codec.http2.Http2ConnectionHandler;
|
||||
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 javax.net.ssl.SSLEngine;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
public Http2OrHttpHandler() {
|
||||
this(MAX_CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
public Http2OrHttpHandler(int maxHttpContentLength) {
|
||||
super(maxHttpContentLength);
|
||||
@Override
|
||||
protected void configureHttp2(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.pipeline().addLast(new HelloWorldHttp2Handler());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SelectedProtocol getProtocol(SSLEngine engine) {
|
||||
String[] protocol = engine.getSession().getProtocol().split(":");
|
||||
if (protocol != null && protocol.length > 1) {
|
||||
SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]);
|
||||
System.err.println("Selected Protocol is " + selectedProtocol);
|
||||
return selectedProtocol;
|
||||
}
|
||||
return SelectedProtocol.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelHandler createHttp1RequestHandler() {
|
||||
return new HelloWorldHttp1Handler("ALPN Negotiation");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Http2ConnectionHandler createHttp2RequestHandler() {
|
||||
return new HelloWorldHttp2Handler();
|
||||
protected void configureHttp1(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.pipeline().addLast(new HttpServerCodec(),
|
||||
new HttpObjectAggregator(MAX_CONTENT_LENGTH),
|
||||
new HelloWorldHttp1Handler("ALPN Negotiation"));
|
||||
}
|
||||
}
|
||||
|
@ -16,46 +16,31 @@
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
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.DefaultHttp2Connection;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
|
||||
import io.netty.handler.codec.http2.Http2ConnectionHandler;
|
||||
import io.netty.handler.codec.http2.Http2OrHttpChooser;
|
||||
import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler;
|
||||
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
/**
|
||||
* 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 Http2OrHttpHandler(int maxHttpContentLength) {
|
||||
super(maxHttpContentLength);
|
||||
}
|
||||
private static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
@Override
|
||||
protected SelectedProtocol getProtocol(SSLEngine engine) {
|
||||
String[] protocol = engine.getSession().getProtocol().split(":");
|
||||
if (protocol != null && protocol.length > 1) {
|
||||
SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]);
|
||||
System.err.println("Selected Protocol is " + selectedProtocol);
|
||||
return selectedProtocol;
|
||||
}
|
||||
return SelectedProtocol.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addHttp2Handlers(ChannelHandlerContext ctx) {
|
||||
protected 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(1024 * 100).build();
|
||||
.validateHttpHeaders(false).maxContentLength(MAX_CONTENT_LENGTH).build();
|
||||
|
||||
ctx.pipeline().addLast("httpToHttp2", new HttpToHttp2ConnectionHandler(connection,
|
||||
// Loggers can be activated for debugging purposes
|
||||
@ -66,12 +51,9 @@ public class Http2OrHttpHandler extends Http2OrHttpChooser {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelHandler createHttp1RequestHandler() {
|
||||
return new FallbackRequestHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Http2ConnectionHandler createHttp2RequestHandler() {
|
||||
return null; // NOOP
|
||||
protected void configureHttp1(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.pipeline().addLast(new HttpServerCodec(),
|
||||
new HttpObjectAggregator(MAX_CONTENT_LENGTH),
|
||||
new FallbackRequestHandler());
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ import javax.net.ssl.SSLException;
|
||||
public class Http2Server {
|
||||
|
||||
public static final int PORT = Integer.parseInt(System.getProperty("http2-port", "8443"));
|
||||
static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
private final EventLoopGroup group;
|
||||
|
||||
@ -63,7 +62,7 @@ public class Http2Server {
|
||||
b.group(group).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new Http2OrHttpHandler(MAX_CONTENT_LENGTH));
|
||||
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new Http2OrHttpHandler());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -51,9 +51,8 @@ import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
*/
|
||||
public final class SpdyClient {
|
||||
|
||||
static final boolean SSL = System.getProperty("ssl") != null;
|
||||
static final String HOST = System.getProperty("host", "127.0.0.1");
|
||||
static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080"));
|
||||
static final int PORT = Integer.parseInt(System.getProperty("port", "8443"));
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Configure SSL.
|
||||
@ -99,9 +98,7 @@ public final class SpdyClient {
|
||||
// Wait until the connection is closed.
|
||||
channel.close().syncUninterruptibly();
|
||||
} finally {
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,17 @@
|
||||
*/
|
||||
package io.netty.example.spdy.server;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Negotiates with the browser if SPDY or HTTP is going to be used. Once decided, the Netty pipeline is setup with
|
||||
@ -26,16 +35,22 @@ public class SpdyOrHttpHandler extends SpdyOrHttpChooser {
|
||||
|
||||
private static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
public SpdyOrHttpHandler() {
|
||||
this(MAX_CONTENT_LENGTH, MAX_CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
public SpdyOrHttpHandler(int maxSpdyContentLength, int maxHttpContentLength) {
|
||||
super(maxSpdyContentLength, maxHttpContentLength);
|
||||
@Override
|
||||
protected void configureSpdy(ChannelHandlerContext ctx, SpdyVersion version) throws Exception {
|
||||
ChannelPipeline p = ctx.pipeline();
|
||||
p.addLast(new SpdyFrameCodec(version));
|
||||
p.addLast(new SpdySessionHandler(version, true));
|
||||
p.addLast(new SpdyHttpEncoder(version));
|
||||
p.addLast(new SpdyHttpDecoder(version, MAX_CONTENT_LENGTH));
|
||||
p.addLast(new SpdyHttpResponseStreamIdHandler());
|
||||
p.addLast(new SpdyServerHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelHandler createHttpRequestHandlerForHttp() {
|
||||
return new SpdyServerHandler();
|
||||
protected void configureHttp1(ChannelHandlerContext ctx) throws Exception {
|
||||
ChannelPipeline p = ctx.pipeline();
|
||||
p.addLast(new HttpServerCodec());
|
||||
p.addLast(new HttpObjectAggregator(MAX_CONTENT_LENGTH));
|
||||
p.addLast(new SpdyServerHandler());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 way to get the application-level protocol name from ALPN or NPN.
|
||||
*/
|
||||
interface ApplicationProtocolAccessor {
|
||||
String getApplicationProtocol();
|
||||
}
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||
* Utility class for application protocol common operations.
|
||||
*/
|
||||
final class ApplicationProtocolUtil {
|
||||
|
||||
private static final int DEFAULT_LIST_SIZE = 2;
|
||||
|
||||
private ApplicationProtocolUtil() {
|
||||
|
@ -24,7 +24,7 @@ import javax.security.cert.X509Certificate;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
final class JdkSslSession implements SSLSession {
|
||||
final class JdkSslSession implements SSLSession, ApplicationProtocolAccessor {
|
||||
private final SSLEngine engine;
|
||||
private volatile String applicationProtocol;
|
||||
|
||||
@ -32,39 +32,22 @@ final class JdkSslSession implements SSLSession {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
void setApplicationProtocol(String applicationProtocol) {
|
||||
if (applicationProtocol != null) {
|
||||
applicationProtocol = applicationProtocol.replace(':', '_');
|
||||
}
|
||||
this.applicationProtocol = applicationProtocol;
|
||||
private SSLSession unwrap() {
|
||||
return engine.getSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
final String protocol = unwrap().getProtocol();
|
||||
final String applicationProtocol = this.applicationProtocol;
|
||||
|
||||
if (applicationProtocol == null) {
|
||||
if (protocol != null) {
|
||||
return protocol.replace(':', '_');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return unwrap().getProtocol();
|
||||
}
|
||||
|
||||
final StringBuilder buf = new StringBuilder(32);
|
||||
if (protocol != null) {
|
||||
buf.append(protocol.replace(':', '_'));
|
||||
buf.append(':');
|
||||
} else {
|
||||
buf.append("null:");
|
||||
}
|
||||
buf.append(applicationProtocol);
|
||||
return buf.toString();
|
||||
@Override
|
||||
public String getApplicationProtocol() {
|
||||
return applicationProtocol;
|
||||
}
|
||||
|
||||
private SSLSession unwrap() {
|
||||
return engine.getSession();
|
||||
void setApplicationProtocol(String applicationProtocol) {
|
||||
this.applicationProtocol = applicationProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -944,7 +944,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_2);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("failed to enable protocols: " + protocols);
|
||||
throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1062,7 +1062,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
private static String selectApplicationProtocol(List<String> protocols,
|
||||
SelectedListenerFailureBehavior behavior,
|
||||
String applicationProtocol) throws SSLException {
|
||||
applicationProtocol = applicationProtocol.replace(':', '_');
|
||||
if (behavior == SelectedListenerFailureBehavior.ACCEPT) {
|
||||
return applicationProtocol;
|
||||
} else {
|
||||
@ -1074,7 +1073,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
if (behavior == SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) {
|
||||
return protocols.get(size - 1);
|
||||
} else {
|
||||
throw new SSLException("Unknown protocol " + applicationProtocol);
|
||||
throw new SSLException("unknown protocol " + applicationProtocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1262,7 +1261,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
private final class OpenSslSession implements SSLSession {
|
||||
private final class OpenSslSession implements SSLSession, ApplicationProtocolAccessor {
|
||||
// SSLSession implementation seems to not need to be thread-safe so no need for volatile etc.
|
||||
private X509Certificate[] x509PeerCerts;
|
||||
|
||||
@ -1481,20 +1480,18 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
String applicationProtocol = OpenSslEngine.this.applicationProtocol;
|
||||
final String version;
|
||||
synchronized (OpenSslEngine.this) {
|
||||
if (destroyed == 0) {
|
||||
version = SSL.getVersion(ssl);
|
||||
return SSL.getVersion(ssl);
|
||||
} else {
|
||||
return StringUtil.EMPTY_STRING;
|
||||
}
|
||||
}
|
||||
if (applicationProtocol == null || applicationProtocol.isEmpty()) {
|
||||
return version;
|
||||
} else {
|
||||
return version + ':' + applicationProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationProtocol() {
|
||||
return applicationProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,6 +48,7 @@ import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||
import javax.net.ssl.SSLEngineResult.Status;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
@ -325,6 +326,20 @@ public class SslHandler extends ByteToMessageDecoder {
|
||||
return engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current application-level protocol.
|
||||
*
|
||||
* @return the protocol name or {@code null} if application-level protocol has not been negotiated
|
||||
*/
|
||||
public String applicationProtocol() {
|
||||
SSLSession sess = engine().getSession();
|
||||
if (!(sess instanceof ApplicationProtocolAccessor)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((ApplicationProtocolAccessor) sess).getApplicationProtocol();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Future} that will get notified once the current TLS handshake completes.
|
||||
*
|
||||
|
@ -58,4 +58,14 @@ public final class SslHandshakeCompletionEvent {
|
||||
public Throwable cause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Throwable cause = cause();
|
||||
if (cause == null) {
|
||||
return "SslHandshakeCompletionEvent(SUCCESS)";
|
||||
} else {
|
||||
return "SslHandshakeCompletionEvent(" + cause + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,14 +262,8 @@ public abstract class SSLEngineTest {
|
||||
private static void verifyApplicationLevelProtocol(Channel channel, String expectedApplicationProtocol) {
|
||||
SslHandler handler = channel.pipeline().get(SslHandler.class);
|
||||
assertNotNull(handler);
|
||||
String[] protocol = handler.engine().getSession().getProtocol().split(":");
|
||||
assertNotNull(protocol);
|
||||
if (expectedApplicationProtocol != null && !expectedApplicationProtocol.isEmpty()) {
|
||||
assertTrue("protocol.length must be greater than 1 but is " + protocol.length, protocol.length > 1);
|
||||
assertEquals(expectedApplicationProtocol, protocol[1]);
|
||||
} else {
|
||||
assertEquals(1, protocol.length);
|
||||
}
|
||||
String appProto = handler.applicationProtocol();
|
||||
assertEquals(appProto, expectedApplicationProtocol);
|
||||
}
|
||||
|
||||
private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch,
|
||||
|
Loading…
Reference in New Issue
Block a user