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);