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.
This commit is contained in:
parent
afb46b926f
commit
0775089496
@ -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<Object> 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.
|
||||
* <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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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<Object> 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.
|
||||
* <p>
|
||||
* A typical implementation of this method will add a new {@link Http2ConnectionHandler} implementation
|
||||
* to the pipeline.
|
||||
* </p>
|
||||
*/
|
||||
protected abstract void configureHttp2(ChannelHandlerContext ctx) 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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() { }
|
||||
}
|
@ -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}<{@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);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </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();
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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 + ')';
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user