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 BiConsumerOpenSsl.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 super SslHandler> 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())");
+ }
+
}