diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index aba2146aaf..7dd41c5040 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -133,6 +133,7 @@ 1.9.22 3.1.0 4.1.27.Final + 2.0.12.Final 1.1.0 42.2.4 2.3.0 @@ -924,6 +925,11 @@ import pom + + io.netty + netty-tcnative-boringssl-static + ${netty-tcnative.version} + io.projectreactor reactor-bom diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc index 3022d8d285..f6b27e9112 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -753,6 +753,22 @@ This error is not fatal, and the application still starts with HTTP/1.1 SSL supp +[[howto-configure-http2-netty]] +==== HTTP/2 with Reactor Netty +The `spring-boot-webflux-starter` is using by default Reactor Netty as a server. +Reactor Netty can be configured for HTTP/2 using the JDK support with JDK 9 or later. +For JDK 8 environments, or for optimal runtime performance, this server also supports +HTTP/2 with native libraries. To enable that, your application needs to have an +additional dependency. + +Spring Boot manages the version for the +`io.netty:netty-tcnative-boringssl-static` "uber jar", containing native libraries for +all platforms. Developers can choose to import only the required dependendencies using +a classifier (see http://netty.io/wiki/forked-tomcat-native.html[the Netty official +documentation]). + + + [[howto-configure-webserver]] === Configure the Web Server diff --git a/spring-boot-project/spring-boot/pom.xml b/spring-boot-project/spring-boot/pom.xml index f8682f0da0..85dca2014d 100644 --- a/spring-boot-project/spring-boot/pom.xml +++ b/spring-boot-project/spring-boot/pom.xml @@ -76,6 +76,11 @@ reactor-netty true + + io.netty + netty-tcnative-boringssl-static + true + io.undertow undertow-servlet @@ -373,6 +378,21 @@ jaybird-jdk18 test + + org.eclipse.jetty + jetty-client + test + + + org.eclipse.jetty.http2 + http2-client + test + + + org.eclipse.jetty.http2 + http2-http-client-transport + test + org.hsqldb hsqldb diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java index 526cda5fef..0bafb6eaba 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import reactor.netty.http.HttpProtocol; import reactor.netty.http.server.HttpServer; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; @@ -110,10 +111,10 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact private HttpServer createHttpServer() { HttpServer server = HttpServer.create().tcpConfiguration( - (tcpServer) -> tcpServer.addressSupplier(() -> getListenAddress())); + (tcpServer) -> tcpServer.addressSupplier(this::getListenAddress)); if (getSsl() != null && getSsl().isEnabled()) { SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(getSsl(), - getSslStoreProvider()); + getHttp2(), getSslStoreProvider()); server = sslServerCustomizer.apply(server); } if (getCompression() != null && getCompression().getEnabled()) { @@ -121,10 +122,23 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact getCompression()); server = compressionCustomizer.apply(server); } + server = server.protocol(listProtocols()); server = (this.useForwardHeaders ? server.forwarded() : server.noForwarded()); return applyCustomizers(server); } + private HttpProtocol[] listProtocols() { + if (getHttp2() != null && getHttp2().isEnabled()) { + if (getSsl() != null && getSsl().isEnabled()) { + return new HttpProtocol[] { HttpProtocol.H2, HttpProtocol.HTTP11 }; + } + else { + return new HttpProtocol[] { HttpProtocol.H2C, HttpProtocol.HTTP11 }; + } + } + return new HttpProtocol[] { HttpProtocol.HTTP11 }; + } + private InetSocketAddress getListenAddress() { if (getAddress() != null) { return new InetSocketAddress(getAddress().getHostAddress(), getPort()); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java index 032547626b..e3b800b539 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java @@ -26,8 +26,9 @@ import javax.net.ssl.TrustManagerFactory; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContextBuilder; import reactor.netty.http.server.HttpServer; -import reactor.netty.tcp.SslProvider.DefaultConfigurationType; +import reactor.netty.tcp.SslProvider; +import org.springframework.boot.web.server.Http2; import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.SslStoreProvider; import org.springframework.util.ResourceUtils; @@ -42,19 +43,26 @@ public class SslServerCustomizer implements NettyServerCustomizer { private final Ssl ssl; + private final Http2 http2; + private final SslStoreProvider sslStoreProvider; - public SslServerCustomizer(Ssl ssl, SslStoreProvider sslStoreProvider) { + public SslServerCustomizer(Ssl ssl, Http2 http2, SslStoreProvider sslStoreProvider) { this.ssl = ssl; + this.http2 = http2; this.sslStoreProvider = sslStoreProvider; } @Override public HttpServer apply(HttpServer server) { try { - return server - .secure((contextSpec) -> contextSpec.sslContext(getContextBuilder()) - .defaultConfiguration(DefaultConfigurationType.NONE)); + return server.secure((contextSpec) -> { + SslProvider.DefaultConfigurationSpec spec = contextSpec + .sslContext(getContextBuilder()); + if (this.http2 != null && this.http2.isEnabled()) { + spec.defaultConfiguration(SslProvider.DefaultConfigurationType.H2); + } + }); } catch (Exception ex) { throw new IllegalStateException(ex);