diff --git a/src/main/java/reactor/netty/http/HttpProtocol.java b/src/main/java/reactor/netty/http/HttpProtocol.java new file mode 100644 index 0000000000..fcd88964ac --- /dev/null +++ b/src/main/java/reactor/netty/http/HttpProtocol.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2011-2018 Pivotal Software Inc, All Rights Reserved. + * + * Licensed 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 reactor.netty.http; + +/** + * An enum defining various Http negotiations between H2, H2c-upgrade, + * H2c-prior-knowledge and Http1.1 + * + * @author Stephane Maldini + */ +public enum HttpProtocol { + + /** + * The default supported HTTP protocol by HttpServer and HttpClient + */ + HTTP11, + + /** + * HTTP 2.0 support with TLS + */ + H2, + + /** + * HTTP 2.0 support with clear-text. + *

If used along with HTTP1 protocol, will support H2c "upgrade": + * Request or consume requests as HTTP 1.1 first, looking for HTTP 2.0 headers + * and {@literal Connection: Upgrade}. A server will typically reply a successful + * 101 status if upgrade is successful or a fallback http 1.1 response. When + * successful the client will start sending HTTP 2.0 traffic. + *

If used without HTTP1 protocol, will support H2c "prior-knowledge": Doesn't + * require {@literal Connection: Upgrade} handshake between a client and server but + * fallback to HTTP 1.1 will not be supported. + */ + H2C +} diff --git a/src/main/java/reactor/netty/http/server/HttpServer.java b/src/main/java/reactor/netty/http/server/HttpServer.java index f5921bf076..d008500112 100644 --- a/src/main/java/reactor/netty/http/server/HttpServer.java +++ b/src/main/java/reactor/netty/http/server/HttpServer.java @@ -34,6 +34,7 @@ import reactor.netty.ConnectionObserver; import reactor.netty.DisposableServer; import reactor.netty.channel.BootstrapHandlers; +import reactor.netty.http.HttpProtocol; import reactor.netty.tcp.SslProvider; import reactor.netty.tcp.TcpServer; import reactor.util.Logger; @@ -50,7 +51,6 @@ * {@code * HttpServer.create() * .host("0.0.0.0") - * .secureSelfSigned() * .handle((req, res) -> res.sendString(Flux.just("hello")) * .bind() * .block(); @@ -281,6 +281,17 @@ public final HttpServer observe(ConnectionObserver observer) { return new HttpServerObserve(this, observer); } + /** + * The HTTP protocol to support. Default is {@link HttpProtocol#HTTP11}. + * + * @param supportedProtocols The various {@link HttpProtocol} this server will support + * + * @return a new {@link HttpServer} + */ + public final HttpServer protocol(HttpProtocol... supportedProtocols) { + return tcpConfiguration(tcpServer -> tcpServer.bootstrap(b -> HttpServerConfiguration.protocols(b, supportedProtocols))); + } + /** * The port to which this server should bind. * diff --git a/src/main/java/reactor/netty/http/server/HttpServerBind.java b/src/main/java/reactor/netty/http/server/HttpServerBind.java index 4cd0df9f91..c6bc3d279a 100644 --- a/src/main/java/reactor/netty/http/server/HttpServerBind.java +++ b/src/main/java/reactor/netty/http/server/HttpServerBind.java @@ -31,8 +31,10 @@ import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpServerUpgradeHandler; +import io.netty.handler.codec.http2.CleartextHttp2ServerUpgradeHandler; import io.netty.handler.codec.http2.Http2CodecUtil; import io.netty.handler.codec.http2.Http2FrameLogger; +import io.netty.handler.codec.http2.Http2MultiplexCodec; import io.netty.handler.codec.http2.Http2MultiplexCodecBuilder; import io.netty.handler.codec.http2.Http2ServerUpgradeCodec; import io.netty.handler.codec.http2.Http2Settings; @@ -110,29 +112,88 @@ public ServerBootstrap apply(ServerBootstrap b) { BootstrapHandlers.channelOperationFactory(b); if (ssl != null) { - return BootstrapHandlers.updateConfiguration(b, - NettyPipeline.HttpInitializer, - new HttpServerSecuredInitializer( - conf.decoder.maxInitialLineLength, - conf.decoder.maxHeaderSize, - conf.decoder.maxChunkSize, - conf.decoder.validateHeaders, - conf.decoder.initialBufferSize, - conf.minCompressionSize, - compressPredicate(conf.compressPredicate, conf.minCompressionSize), - conf.forwarded)); + if ((conf.protocols & HttpServerConfiguration.h2c) == HttpServerConfiguration.h2c) { + throw new IllegalArgumentException("Configured H2 Clear-Text protocol " + + "with TLS. Use the non clear-text h2 protocol via " + + "HttpServer#protocol or disable TLS" + + " via HttpServer#tcpConfiguration(tcp -> tcp.noSSL())"); + } + if ((conf.protocols & HttpServerConfiguration.h11orH2) == HttpServerConfiguration.h11orH2) { + return BootstrapHandlers.updateConfiguration(b, + NettyPipeline.HttpInitializer, + new Http1OrH2Initializer(conf.decoder.maxInitialLineLength, + conf.decoder.maxHeaderSize, + conf.decoder.maxChunkSize, + conf.decoder.validateHeaders, + conf.decoder.initialBufferSize, + conf.minCompressionSize, + compressPredicate(conf.compressPredicate, conf.minCompressionSize), + conf.forwarded)); + } + if ((conf.protocols & HttpServerConfiguration.h11) == HttpServerConfiguration.h11) { + return BootstrapHandlers.updateConfiguration(b, + NettyPipeline.HttpInitializer, + new Http1Initializer(conf.decoder.maxInitialLineLength, + conf.decoder.maxHeaderSize, + conf.decoder.maxChunkSize, + conf.decoder.validateHeaders, + conf.decoder.initialBufferSize, + conf.minCompressionSize, + compressPredicate(conf.compressPredicate, conf.minCompressionSize), + conf.forwarded)); + } + if ((conf.protocols & HttpServerConfiguration.h2) == HttpServerConfiguration.h2) { + return BootstrapHandlers.updateConfiguration(b, + NettyPipeline.HttpInitializer, + new H2Initializer( + conf.decoder.validateHeaders, + conf.minCompressionSize, + compressPredicate(conf.compressPredicate, conf.minCompressionSize), + conf.forwarded)); + } + } + else { + if ((conf.protocols & HttpServerConfiguration.h2) == HttpServerConfiguration.h2) { + throw new IllegalArgumentException( + "Configured H2 protocol without TLS. Use" + " a clear-text h2 protocol via HttpServer#protocol or configure TLS" + " via HttpServer#secure"); + } + if ((conf.protocols & HttpServerConfiguration.h11orH2c) == HttpServerConfiguration.h11orH2c) { + return BootstrapHandlers.updateConfiguration(b, + NettyPipeline.HttpInitializer, + new Http1OrH2CleartextInitializer(conf.decoder.maxInitialLineLength, + conf.decoder.maxHeaderSize, + conf.decoder.maxChunkSize, + conf.decoder.validateHeaders, + conf.decoder.initialBufferSize, + conf.minCompressionSize, + compressPredicate(conf.compressPredicate, conf.minCompressionSize), + conf.forwarded)); + } + if ((conf.protocols & HttpServerConfiguration.h11) == HttpServerConfiguration.h11) { + return BootstrapHandlers.updateConfiguration(b, + NettyPipeline.HttpInitializer, + new Http1Initializer(conf.decoder.maxInitialLineLength, + conf.decoder.maxHeaderSize, + conf.decoder.maxChunkSize, + conf.decoder.validateHeaders, + conf.decoder.initialBufferSize, + conf.minCompressionSize, + compressPredicate(conf.compressPredicate, conf.minCompressionSize), + conf.forwarded)); + } + if ((conf.protocols & HttpServerConfiguration.h2c) == HttpServerConfiguration.h2c) { + return BootstrapHandlers.updateConfiguration(b, + NettyPipeline.HttpInitializer, + new H2CleartextInitializer( + conf.decoder.validateHeaders, + conf.minCompressionSize, + compressPredicate(conf.compressPredicate, conf.minCompressionSize), + conf.forwarded)); + } } - return BootstrapHandlers.updateConfiguration(b, - NettyPipeline.HttpInitializer, - new HttpServerInitializer( - conf.decoder.maxInitialLineLength, - conf.decoder.maxHeaderSize, - conf.decoder.maxChunkSize, - conf.decoder.validateHeaders, - conf.decoder.initialBufferSize, - conf.minCompressionSize, - compressPredicate(conf.compressPredicate, conf.minCompressionSize), - conf.forwarded)); + throw new IllegalArgumentException("An unknown HttpServer#protocol " + + "configuration has been provided: "+String.format("0x%x", conf + .protocols)); } @Nullable @@ -183,7 +244,56 @@ static void addStreamHandlers(Channel ch, ConnectionObserver listener, boolean r } } - static final class HttpServerInitializer + + static final class Http1Initializer + implements BiConsumer { + + final int line; + final int header; + final int chunk; + final boolean validate; + final int buffer; + final int minCompressionSize; + final BiPredicate compressPredicate; + final boolean forwarded; + + Http1Initializer(int line, + int header, + int chunk, + boolean validate, + int buffer, + int minCompressionSize, + @Nullable BiPredicate compressPredicate, + boolean forwarded) { + this.line = line; + this.header = header; + this.chunk = chunk; + this.validate = validate; + this.buffer = buffer; + this.minCompressionSize = minCompressionSize; + this.compressPredicate = compressPredicate; + this.forwarded = forwarded; + } + + @Override + public void accept(ConnectionObserver listener, Channel channel) { + ChannelPipeline p = channel.pipeline(); + + p.addLast(NettyPipeline.HttpCodec, new HttpServerCodec(line, header, chunk, validate, buffer)); + + boolean alwaysCompress = compressPredicate == null && minCompressionSize == 0; + + if (alwaysCompress) { + p.addLast(NettyPipeline.CompressionHandler, + new SimpleCompressionHandler()); + } + + p.addLast(NettyPipeline.HttpTrafficHandler, + new HttpTrafficHandler(listener, forwarded, compressPredicate)); + } + } + + static final class Http1OrH2CleartextInitializer implements BiConsumer { final int line; @@ -195,7 +305,7 @@ static final class HttpServerInitializer final BiPredicate compressPredicate; final boolean forwarded; - HttpServerInitializer(int line, + Http1OrH2CleartextInitializer(int line, int header, int chunk, boolean validate, @@ -220,11 +330,20 @@ public void accept(ConnectionObserver listener, Channel channel) { HttpServerCodec httpServerCodec = new HttpServerCodec(line, header, chunk, validate, buffer); - p.addLast(NettyPipeline.HttpCodec, httpServerCodec) - .addLast(new HttpServerUpgradeHandler(httpServerCodec, - new UpgradeCodecFactoryImpl(this, - listener, - p.get(NettyPipeline.LoggingHandler) != null))); +// p.addLast(NettyPipeline.HttpCodec, httpServerCodec) +// .addLast(new HttpServerUpgradeHandler(httpServerCodec, +// new Http1OrH2CleartextCodec(this,/;poƵ +// listener, +// p.get(NettyPipeline.LoggingHandler) != null))); + + Http1OrH2CleartextCodec + upgrader = new Http1OrH2CleartextCodec(this, listener, p.get(NettyPipeline.LoggingHandler) != null); + + + final CleartextHttp2ServerUpgradeHandler h2cUpgradeHandler = + new CleartextHttp2ServerUpgradeHandler(httpServerCodec, new HttpServerUpgradeHandler(httpServerCodec, upgrader), upgrader.multiplexCodec); + + p.addLast(NettyPipeline.HttpCodec, h2cUpgradeHandler); boolean alwaysCompress = compressPredicate == null && minCompressionSize == 0; @@ -243,17 +362,27 @@ public void accept(ConnectionObserver listener, Channel channel) { * or cleartext upgrade */ @ChannelHandler.Sharable - static final class UpgradeCodecFactoryImpl extends ChannelInitializer + static final class Http1OrH2CleartextCodec extends ChannelInitializer implements HttpServerUpgradeHandler.UpgradeCodecFactory { - final HttpServerInitializer parent; - final ConnectionObserver listener; - final boolean debug; + final Http1OrH2CleartextInitializer parent; + final ConnectionObserver listener; + final Http2MultiplexCodec multiplexCodec; - UpgradeCodecFactoryImpl(HttpServerInitializer parent, ConnectionObserver listener, boolean debug) { + Http1OrH2CleartextCodec(Http1OrH2CleartextInitializer parent, ConnectionObserver listener, boolean debug) { this.parent = parent; this.listener = listener; - this.debug = debug; + Http2MultiplexCodecBuilder http2MultiplexCodecBuilder = + Http2MultiplexCodecBuilder.forServer(this) + .validateHeaders(parent.validate) + .initialSettings(Http2Settings.defaultSettings()); + + if (debug) { + http2MultiplexCodecBuilder.frameLogger(new Http2FrameLogger( + LogLevel.DEBUG, + "reactor.netty.http.server.h2.cleartext")); + } + this.multiplexCodec = http2MultiplexCodecBuilder.build(); } /** @@ -269,16 +398,7 @@ protected void initChannel(Channel ch) { public HttpServerUpgradeHandler.UpgradeCodec newUpgradeCodec(CharSequence protocol) { if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) { - Http2MultiplexCodecBuilder http2MultiplexCodecBuilder = - Http2MultiplexCodecBuilder.forServer(this) - .initialSettings(Http2Settings.defaultSettings()); - - if (debug) { - http2MultiplexCodecBuilder.frameLogger(new Http2FrameLogger( - LogLevel.DEBUG, - HttpServer.class)); - } - return new Http2ServerUpgradeCodec(http2MultiplexCodecBuilder.build()); + return new Http2ServerUpgradeCodec(multiplexCodec); } else { return null; @@ -286,12 +406,50 @@ public HttpServerUpgradeHandler.UpgradeCodec newUpgradeCodec(CharSequence protoc } } + static final class H2CleartextInitializer + implements BiConsumer { + + final boolean validate; + final int minCompressionSize; + final BiPredicate compressPredicate; + final boolean forwarded; + + H2CleartextInitializer( + boolean validate, + int minCompressionSize, + @Nullable BiPredicate compressPredicate, + boolean forwarded) { + this.validate = validate; + this.minCompressionSize = minCompressionSize; + this.compressPredicate = compressPredicate; + this.forwarded = forwarded; + } + + @Override + public void accept(ConnectionObserver listener, Channel channel) { + ChannelPipeline p = channel.pipeline(); + + Http2MultiplexCodecBuilder http2MultiplexCodecBuilder = + Http2MultiplexCodecBuilder.forServer(new Http2StreamInitializer(listener, forwarded)) + .validateHeaders(validate) + .initialSettings(Http2Settings.defaultSettings()); + + if (p.get(NettyPipeline.LoggingHandler) != null) { + http2MultiplexCodecBuilder.frameLogger(new Http2FrameLogger( + LogLevel.DEBUG, + "reactor.netty.http.server.h2.cleartext")); + } + + p.addLast(NettyPipeline.HttpCodec, http2MultiplexCodecBuilder.build()); + + channel.read(); + } + } + /** * Initialize Http1 - Http2 pipeline configuration using SSL detection */ - @ChannelHandler.Sharable - static final class HttpServerSecuredInitializer extends ApplicationProtocolNegotiationHandler - implements BiConsumer { + static final class Http1OrH2Initializer implements BiConsumer { final int line; final int header; @@ -302,10 +460,7 @@ static final class HttpServerSecuredInitializer extends ApplicationProtocolNegot final BiPredicate compressPredicate; final boolean forwarded; - Http2StreamInitializer initializer; - ConnectionObserver listener; - - HttpServerSecuredInitializer( + Http1OrH2Initializer( int line, int header, int chunk, @@ -314,7 +469,6 @@ static final class HttpServerSecuredInitializer extends ApplicationProtocolNegot int minCompressionSize, @Nullable BiPredicate compressPredicate, boolean forwarded) { - super(ApplicationProtocolNames.HTTP_1_1); this.line = line; this.header = header; this.chunk = chunk; @@ -325,11 +479,22 @@ static final class HttpServerSecuredInitializer extends ApplicationProtocolNegot this.forwarded = forwarded; } - @Override public void accept(ConnectionObserver observer, Channel channel) { - listener = observer; - channel.pipeline().addLast(this); + channel.pipeline() + .addLast(new Http1OrH2Codec(this, observer)); + } + } + + static final class Http1OrH2Codec extends ApplicationProtocolNegotiationHandler { + + final ConnectionObserver listener; + final Http1OrH2Initializer parent; + + Http1OrH2Codec(Http1OrH2Initializer parent, ConnectionObserver listener) { + super(ApplicationProtocolNames.HTTP_1_1); + this.listener = listener; + this.parent = parent; } @Override @@ -340,17 +505,16 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { p.remove(NettyPipeline.ReactiveBridge); - initializer = new Http2StreamInitializer(this, listener); - Http2MultiplexCodecBuilder http2MultiplexCodecBuilder = - Http2MultiplexCodecBuilder.forServer(initializer) + Http2MultiplexCodecBuilder.forServer(new Http2StreamInitializer(listener, parent.forwarded)) .initialSettings(Http2Settings.defaultSettings()); if (p.get(NettyPipeline.LoggingHandler) != null) { - http2MultiplexCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG, HttpServer.class)); + http2MultiplexCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG, "reactor.netty.http.server.h2.secure")); } - p.addLast(http2MultiplexCodecBuilder.build()); + p.addLast(NettyPipeline.HttpCodec, http2MultiplexCodecBuilder.build()); + return; } @@ -358,12 +522,12 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { p.addBefore(NettyPipeline.ReactiveBridge, NettyPipeline.HttpCodec, - new HttpServerCodec(line, header, chunk, validate, buffer)) + new HttpServerCodec(parent.line, parent.header, parent.chunk, parent.validate, parent.buffer)) .addBefore(NettyPipeline.ReactiveBridge, - NettyPipeline.HttpTrafficHandler, - new HttpTrafficHandler( listener, forwarded, compressPredicate)); + NettyPipeline.HttpTrafficHandler, + new HttpTrafficHandler( listener, parent.forwarded, parent.compressPredicate)); - boolean alwaysCompress = compressPredicate == null && minCompressionSize == 0; + boolean alwaysCompress = parent.compressPredicate == null && parent.minCompressionSize == 0; if (alwaysCompress) { p.addBefore(NettyPipeline.HttpTrafficHandler, @@ -375,23 +539,59 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { throw new IllegalStateException("unknown protocol: " + protocol); } + } + + static final class H2Initializer + implements BiConsumer { + + final boolean validate; + final int minCompressionSize; + final BiPredicate compressPredicate; + final boolean forwarded; + + H2Initializer( + boolean validate, + int minCompressionSize, + @Nullable BiPredicate compressPredicate, + boolean forwarded) { + this.validate = validate; + this.minCompressionSize = minCompressionSize; + this.compressPredicate = compressPredicate; + this.forwarded = forwarded; + } + + @Override + public void accept(ConnectionObserver listener, Channel channel) { + ChannelPipeline p = channel.pipeline(); + Http2MultiplexCodecBuilder http2MultiplexCodecBuilder = + Http2MultiplexCodecBuilder.forServer(new Http2StreamInitializer + (listener, forwarded)) + .validateHeaders(validate) + .initialSettings(Http2Settings.defaultSettings()); + + if (p.get(NettyPipeline.LoggingHandler) != null) { + http2MultiplexCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG, "reactor.netty.http.server.h2.secured")); + } + + p.addLast(NettyPipeline.HttpCodec, http2MultiplexCodecBuilder.build()); + } } static final class Http2StreamInitializer extends ChannelInitializer { - final HttpServerSecuredInitializer parent; - final ConnectionObserver listener; + final boolean forwarded; + final ConnectionObserver listener; - Http2StreamInitializer(HttpServerSecuredInitializer parent, - ConnectionObserver listener) { - this.parent = parent; + Http2StreamInitializer(ConnectionObserver listener, boolean forwarded) { + this.forwarded = forwarded; this.listener = listener; } @Override protected void initChannel(Channel ch) { - addStreamHandlers(ch, listener, parent.forwarded); + addStreamHandlers(ch, listener, forwarded); } } + } diff --git a/src/main/java/reactor/netty/http/server/HttpServerConfiguration.java b/src/main/java/reactor/netty/http/server/HttpServerConfiguration.java index 5c71ebaa7f..23fd884476 100644 --- a/src/main/java/reactor/netty/http/server/HttpServerConfiguration.java +++ b/src/main/java/reactor/netty/http/server/HttpServerConfiguration.java @@ -21,6 +21,7 @@ import io.netty.bootstrap.ServerBootstrap; import io.netty.util.AttributeKey; +import reactor.netty.http.HttpProtocol; /** * @author Stephane Maldini @@ -32,11 +33,15 @@ final class HttpServerConfiguration { static final AttributeKey CONF_KEY = AttributeKey.newInstance("httpServerConf"); - int minCompressionSize = -1; + static final HttpProtocol[] HTTP11 = {HttpProtocol.HTTP11}; + BiPredicate compressPredicate = null; - boolean forwarded = false; - HttpRequestDecoderSpec decoder = - new HttpRequestDecoderSpec(); + + int minCompressionSize = -1; + boolean forwarded = false; + HttpRequestDecoderSpec decoder = new HttpRequestDecoderSpec(); + int protocols = h11; + static HttpServerConfiguration getAndClean(ServerBootstrap b) { HttpServerConfiguration hcc = (HttpServerConfiguration) b.config() @@ -92,6 +97,25 @@ static ServerBootstrap compressSize(ServerBootstrap b, int minCompressionSize) { return b; } + static ServerBootstrap protocols(ServerBootstrap b, HttpProtocol... protocols) { + int _protocols = 0; + + for (HttpProtocol p : protocols) { + if (p == HttpProtocol.HTTP11) { + _protocols |= h11; + } + else if (p == HttpProtocol.H2) { + _protocols |= h2; + } + else if (p == HttpProtocol.H2C) { + _protocols |= h2c; + } + } + + getOrCreate(b).protocols = _protocols; + return b; + } + static ServerBootstrap compressPredicate(ServerBootstrap b, BiPredicate compressPredicate) { getOrCreate(b).compressPredicate = compressPredicate; @@ -103,4 +127,10 @@ static ServerBootstrap decoder(ServerBootstrap b, getOrCreate(b).decoder = decoder; return b; } + + static final int h11 = 0b100; + static final int h2 = 0b010; + static final int h2c = 0b001; + static final int h11orH2c = h11 | h2c; + static final int h11orH2 = h11 | h2; } diff --git a/src/main/java/reactor/netty/http/server/HttpServerSecure.java b/src/main/java/reactor/netty/http/server/HttpServerSecure.java index 3b3a5a0482..fe184e4081 100644 --- a/src/main/java/reactor/netty/http/server/HttpServerSecure.java +++ b/src/main/java/reactor/netty/http/server/HttpServerSecure.java @@ -24,7 +24,8 @@ /** * @author Stephane Maldini */ -final class HttpServerSecure extends HttpServerOperator { +final class HttpServerSecure extends HttpServerOperator + implements Consumer { final Consumer sslProviderBuilder; @@ -35,8 +36,16 @@ final class HttpServerSecure extends HttpServerOperator { this.sslProviderBuilder = sslProviderBuilder; } + @Override + public void accept(SslProvider.SslContextSpec spec) { + if (spec instanceof SslProvider.DefaultConfigurationSpec) { + ((SslProvider.DefaultConfigurationSpec)spec).defaultConfiguration(SslProvider.DefaultConfigurationType.HTTP); + } + sslProviderBuilder.accept(spec); + } + @Override protected TcpServer tcpConfiguration() { - return source.tcpConfiguration().secure(sslProviderBuilder); + return source.tcpConfiguration().secure(this); } } diff --git a/src/main/java/reactor/netty/tcp/SslProvider.java b/src/main/java/reactor/netty/tcp/SslProvider.java index 875c75ab58..e940effa88 100644 --- a/src/main/java/reactor/netty/tcp/SslProvider.java +++ b/src/main/java/reactor/netty/tcp/SslProvider.java @@ -149,6 +149,137 @@ public static Bootstrap removeSslSupport(Bootstrap b) { return b; } + public interface Builder { + + /** + * Set a configurator callback to mutate any property from the provided + * {@link SslHandler} + * + * @param handlerConfigurator A callback given the generated {@link SslHandler} + * + * @return {@literal this} + */ + Builder handlerConfigurator(Consumer handlerConfigurator); + + /** + * Set the options to use for configuring SSL handshake timeout. Default to 10000 ms. + * + * @param handshakeTimeout The timeout {@link Duration} + * + * @return {@literal this} + */ + Builder handshakeTimeout(Duration handshakeTimeout); + + /** + * Set the options to use for configuring SSL handshake timeout. Default to 10000 ms. + * + * @param handshakeTimeoutMillis The timeout in milliseconds + * + * @return {@literal this} + */ + Builder handshakeTimeoutMillis(long handshakeTimeoutMillis); + + /** + * Set the options to use for configuring SSL close_notify flush timeout. Default to 3000 ms. + * + * @param closeNotifyFlushTimeout The timeout {@link Duration} + * + * @return {@literal this} + */ + Builder closeNotifyFlushTimeout(Duration closeNotifyFlushTimeout); + + /** + * Set the options to use for configuring SSL close_notify flush timeout. Default to 3000 ms. + * + * @param closeNotifyFlushTimeoutMillis The timeout in milliseconds + * + * @return {@literal this} + */ + Builder closeNotifyFlushTimeoutMillis(long closeNotifyFlushTimeoutMillis); + + /** + * Set the options to use for configuring SSL close_notify read timeout. Default to 0 ms. + * + * @param closeNotifyReadTimeout The timeout {@link Duration} + * + * @return {@literal this} + */ + Builder closeNotifyReadTimeout(Duration closeNotifyReadTimeout); + + /** + * Set the options to use for configuring SSL close_notify read timeout. Default to 0 ms. + * + * @param closeNotifyReadTimeoutMillis The timeout in milliseconds + * + * @return {@literal this} + */ + Builder closeNotifyReadTimeoutMillis(long closeNotifyReadTimeoutMillis); + + /** + * Builds new SslProvider + * + * @return builds new SslProvider + */ + SslProvider build(); + } + + public interface SslContextSpec { + + /** + * The SslContext to set when configuring SSL + * + * @param sslContext The context to set when configuring SSL + * + * @return {@literal this} + */ + Builder sslContext(SslContext sslContext); + + /** + * The SslContextBuilder for building a new {@link SslContext}. + * + * @return {@literal this} + */ + DefaultConfigurationSpec sslContext(SslContextBuilder sslCtxBuilder); + + } + + /** + * Default configuration that will be applied to the provided + * {@link SslContextBuilder} + */ + public enum DefaultConfigurationType { + /** + * There will be no default configuration + */ + NONE, + /** + * {@link io.netty.handler.ssl.SslProvider} will be set depending on + * OpenSsl.isAlpnSupported() + */ + TCP, + /** + * {@link io.netty.handler.ssl.SslProvider} will be set depending on + * OpenSsl.isAlpnSupported(), + * {@link Http2SecurityUtil#CIPHERS}, + * ALPN support, + * HTTP/1.1 and HTTP/2 support + * + */ + HTTP + } + + public interface DefaultConfigurationSpec { + + /** + * Default configuration type that will be applied to the provided + * {@link SslContextBuilder} + * + * @param type The default configuration type. + * @return {@code this} + */ + Builder defaultConfiguration(DefaultConfigurationType type); + } + final SslContext sslContext; final long handshakeTimeoutMillis; final long closeNotifyFlushTimeoutMillis; @@ -224,7 +355,6 @@ public void configure(SslHandler sslHandler) { } } - public String asSimpleString() { return toString(); } @@ -344,137 +474,6 @@ public SslProvider build() { } } - public interface Builder { - - /** - * Set a configurator callback to mutate any property from the provided - * {@link SslHandler} - * - * @param handlerConfigurator A callback given the generated {@link SslHandler} - * - * @return {@literal this} - */ - Builder handlerConfigurator(Consumer handlerConfigurator); - - /** - * Set the options to use for configuring SSL handshake timeout. Default to 10000 ms. - * - * @param handshakeTimeout The timeout {@link Duration} - * - * @return {@literal this} - */ - Builder handshakeTimeout(Duration handshakeTimeout); - - /** - * Set the options to use for configuring SSL handshake timeout. Default to 10000 ms. - * - * @param handshakeTimeoutMillis The timeout in milliseconds - * - * @return {@literal this} - */ - Builder handshakeTimeoutMillis(long handshakeTimeoutMillis); - - /** - * Set the options to use for configuring SSL close_notify flush timeout. Default to 3000 ms. - * - * @param closeNotifyFlushTimeout The timeout {@link Duration} - * - * @return {@literal this} - */ - Builder closeNotifyFlushTimeout(Duration closeNotifyFlushTimeout); - - /** - * Set the options to use for configuring SSL close_notify flush timeout. Default to 3000 ms. - * - * @param closeNotifyFlushTimeoutMillis The timeout in milliseconds - * - * @return {@literal this} - */ - Builder closeNotifyFlushTimeoutMillis(long closeNotifyFlushTimeoutMillis); - - /** - * Set the options to use for configuring SSL close_notify read timeout. Default to 0 ms. - * - * @param closeNotifyReadTimeout The timeout {@link Duration} - * - * @return {@literal this} - */ - Builder closeNotifyReadTimeout(Duration closeNotifyReadTimeout); - - /** - * Set the options to use for configuring SSL close_notify read timeout. Default to 0 ms. - * - * @param closeNotifyReadTimeoutMillis The timeout in milliseconds - * - * @return {@literal this} - */ - Builder closeNotifyReadTimeoutMillis(long closeNotifyReadTimeoutMillis); - - /** - * Builds new SslProvider - * - * @return builds new SslProvider - */ - SslProvider build(); - } - - public interface SslContextSpec { - - /** - * The SslContext to set when configuring SSL - * - * @param sslContext The context to set when configuring SSL - * - * @return {@literal this} - */ - Builder sslContext(SslContext sslContext); - - /** - * The SslContextBuilder for building a new {@link SslContext}. - * - * @return {@literal this} - */ - DefaultConfigurationSpec sslContext(SslContextBuilder sslCtxBuilder); - - } - - /** - * Default configuration that will be applied to the provided - * {@link SslContextBuilder} - */ - public enum DefaultConfigurationType { - /** - * There will be no default configuration - */ - NONE, - /** - * {@link io.netty.handler.ssl.SslProvider} will be set depending on - * OpenSsl.isAlpnSupported() - */ - TCP, - /** - * {@link io.netty.handler.ssl.SslProvider} will be set depending on - * OpenSsl.isAlpnSupported(), - * {@link Http2SecurityUtil#CIPHERS}, - * ALPN support, - * HTTP/1.1 and HTTP/2 support - * - */ - HTTP - } - - public interface DefaultConfigurationSpec { - - /** - * Default configuration type that will be applied to the provided - * {@link SslContextBuilder} - * - * @param type The default configuration type. - * @return {@code this} - */ - Builder defaultConfiguration(DefaultConfigurationType type); - } - static ServerBootstrap removeSslSupport(ServerBootstrap b) { BootstrapHandlers.removeConfiguration(b, NettyPipeline.SslHandler); return b; diff --git a/src/main/java/reactor/netty/tcp/TcpServer.java b/src/main/java/reactor/netty/tcp/TcpServer.java index d41c6e3825..7c1d1a0e3c 100644 --- a/src/main/java/reactor/netty/tcp/TcpServer.java +++ b/src/main/java/reactor/netty/tcp/TcpServer.java @@ -66,7 +66,6 @@ * .doOnUnbound(stopMetrics) * .host("127.0.0.1") * .port(1234) - * .secureSelfSigned() * .bind() * .block() * } diff --git a/src/test/java/reactor/netty/http/HttpTests.java b/src/test/java/reactor/netty/http/HttpTests.java index 761a585317..311427162c 100644 --- a/src/test/java/reactor/netty/http/HttpTests.java +++ b/src/test/java/reactor/netty/http/HttpTests.java @@ -532,8 +532,7 @@ public void testHttpSsl() throws Exception { DisposableServer server = HttpServer.create() .port(8080) - .secure(sslContextSpec -> sslContextSpec.sslContext(serverOptions) - .defaultConfiguration(SslProvider.DefaultConfigurationType.HTTP)) + .secure(sslContextSpec -> sslContextSpec.sslContext(serverOptions)) .handle((req, res) -> res.sendString(Mono.just("Hello"))) .wiretap() .bindNow(); @@ -571,9 +570,69 @@ public void testHttpToHttp2ClearText() { @Test @Ignore - public void testHttp() throws Exception { + public void testH2PriorKnowledge() throws Exception { +// SelfSignedCertificate cert = new SelfSignedCertificate(); +// SslContextBuilder serverOptions = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()); + + DisposableServer server = + HttpServer.create() + .protocol(HttpProtocol.H2C) + .port(8080) + .handle((req, res) -> res.sendString(Mono.just("Hello"))) + .wiretap() + .bindNow(); + + new CountDownLatch(1).await(); + server.disposeNow(); + } + + @Test + @Ignore + public void testHttp1or2() throws Exception { +// SelfSignedCertificate cert = new SelfSignedCertificate(); +// SslContextBuilder serverOptions = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()); + + DisposableServer server = + HttpServer.create() + .protocol(HttpProtocol.H2C, HttpProtocol.HTTP11) + .port(8080) + .handle((req, res) -> res.sendString(Mono.just("Hello"))) + .wiretap() + .bindNow(); + + new CountDownLatch(1).await(); + server.disposeNow(); + } + + @Test + @Ignore + public void testH2Secure() throws Exception { + SelfSignedCertificate cert = new SelfSignedCertificate(); + SslContextBuilder serverOptions = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()); + + DisposableServer server = + HttpServer.create() + .protocol(HttpProtocol.H2) + .secure(ssl -> ssl.sslContext(serverOptions)) + .port(8080) + .handle((req, res) -> res.sendString(Mono.just("Hello"))) + .wiretap() + .bindNow(); + + new CountDownLatch(1).await(); + server.disposeNow(); + } + + @Test + @Ignore + public void testHttp1or2Secure() throws Exception { + SelfSignedCertificate cert = new SelfSignedCertificate(); + SslContextBuilder serverOptions = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()); + DisposableServer server = HttpServer.create() + .protocol(HttpProtocol.H2, HttpProtocol.HTTP11) + .secure(ssl -> ssl.sslContext(serverOptions)) .port(8080) .handle((req, res) -> res.sendString(Mono.just("Hello"))) .wiretap() @@ -583,4 +642,32 @@ public void testHttp() throws Exception { server.disposeNow(); } + @Test + public void testHttpNoSslH2Fails() { + StepVerifier.create( + HttpServer.create() + .protocol(HttpProtocol.H2) + .handle((req, res) -> res.sendString(Mono.just("Hello"))) + .wiretap() + .bind() + ).verifyErrorMessage("Configured H2 protocol without TLS. Use" + + " a clear-text h2 protocol via HttpServer#protocol or configure TLS" + + " via HttpServer#secure"); + } + + @Test + public void testHttpSslH2CFails() throws Exception { + SelfSignedCertificate cert = new SelfSignedCertificate(); + SslContextBuilder serverOptions = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()); + + StepVerifier.create( + HttpServer.create() + .protocol(HttpProtocol.H2C) + .secure(ssl -> ssl.sslContext(serverOptions)) + .handle((req, res) -> res.sendString(Mono.just("Hello"))) + .wiretap() + .bind() + ).verifyErrorMessage("Configured H2 Clear-Text protocol with TLS. Use the non clear-text h2 protocol via HttpServer#protocol or disable TLS via HttpServer#tcpConfiguration(tcp -> tcp.noSSL())"); + } + }