Skip to content

Commit

Permalink
Auto-configure H2C when HTTP/2 is enabled and SSL is disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
wilkinsona committed Apr 12, 2021
1 parent 288e86d commit 713c0fc
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 105 deletions.
85 changes: 13 additions & 72 deletions spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -594,26 +594,23 @@ We recommend using `application.properties` to configure HTTPS, as the HTTP conn
[[howto-configure-http2]]
=== Configure HTTP/2
You can enable HTTP/2 support in your Spring Boot application with the configprop:server.http2.enabled[] configuration property.
This support depends on the chosen web server and the application environment, since that protocol is not supported out-of-the-box by all JDK8 releases.
Both `h2` (HTTP/2 over TLS) and `h2c` (HTTP/2 over TCP) are supported.
To use `h2`, SSL must also be enabled.
When SSL is not enabled, `h2c` will be used.
The details of the `h2` support depend on the chosen web server and the application environment, since that protocol is not supported out-of-the-box by all JDK 8 releases.

[NOTE]
====
Spring Boot does not advise using `h2c`, the cleartext version of the HTTP/2 protocol.
As a result, the following sections require you to <<howto-configure-ssl, configure SSL first>>.
If you still choose to use `h2c`, you can check <<howto-configure-http2-h2c, the dedicated section>>.
====


[[howto-configure-http2-tomcat]]
==== HTTP/2 with Tomcat
Spring Boot ships by default with Tomcat 9.0.x which supports HTTP/2 out of the box when using JDK 9 or later.
Alternatively, HTTP/2 can be used on JDK 8 if the `libtcnative` library and its dependencies are installed on the host operating system.
Spring Boot ships by default with Tomcat 9.0.x which supports `h2c` out of the box and `h2` out of the box when using JDK 9 or later.
Alternatively, `h2` can be used on JDK 8 if the `libtcnative` library and its dependencies are installed on the host operating system.

The library directory must be made available, if not already, to the JVM library path.
You can do so with a JVM argument such as `-Djava.library.path=/usr/local/opt/tomcat-native/lib`.
More on this in the https://tomcat.apache.org/tomcat-9.0-doc/apr.html[official Tomcat documentation].

Starting Tomcat 9.0.x on JDK 8 without that native support logs the following error:
Starting Tomcat 9.0.x on JDK 8 with HTTP/2 and SSL enabled but without that native support logs the following error:

[indent=0,subs="attributes"]
----
Expand All @@ -626,7 +623,8 @@ This error is not fatal, and the application still starts with HTTP/1.1 SSL supp
[[howto-configure-http2-jetty]]
==== HTTP/2 with Jetty
For HTTP/2 support, Jetty requires the additional `org.eclipse.jetty.http2:http2-server` dependency.
Now depending on your deployment, you also need to choose other dependencies:
To use `h2c` no other dependencies are required.
To use `h2`, you also need to choose one of the following dependencies, depending on your deployment:

* `org.eclipse.jetty:jetty-alpn-java-server` for applications running on JDK9+
* `org.eclipse.jetty:jetty-alpn-openjdk8-server` for applications running on JDK8u252+
Expand All @@ -637,8 +635,9 @@ Now depending on your deployment, you also need to choose other dependencies:
[[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.
Reactor Netty supports `h2c` using JDK 8 or later with no additional dependencies.
Reactor Netty supports `h2` using the JDK support with JDK 9 or later.
For JDK 8 environments, or for optimal runtime performance, this server also supports `h2` 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.
Expand All @@ -648,65 +647,7 @@ Developers can choose to import only the required dependencies using a classifie

[[howto-configure-http2-undertow]]
==== HTTP/2 with Undertow
As of Undertow 1.4.0+, HTTP/2 is supported without any additional requirement on JDK8.



[[howto-configure-http2-h2c]]
==== HTTP/2 Cleartext with supported servers
To enable HTTP/2 with cleartext support, you need to leave the configprop:server.http2.enabled[] property set to `false`,
and instead apply a customizer specific to your choice of server:

For Tomcat, we need to add an upgrade protocol:

[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"]
----
@Bean
public TomcatConnectorCustomizer connectorCustomizer() {
return (connector) -> connector.addUpgradeProtocol(new Http2Protocol());
}
----

For Jetty, we need to add a connection factory to the existing connector:

[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"]
----
@Bean
public JettyServerCustomizer serverCustomizer() {
return (server) -> {
HttpConfiguration configuration = new HttpConfiguration();
configuration.setSendServerVersion(false);
Arrays.stream(server.getConnectors())
.filter(connector -> connector instanceof ServerConnector)
.map(ServerConnector.class::cast)
.forEach(connector -> {
connector.addConnectionFactory(new HTTP2CServerConnectionFactory(configuration));
});
};
}
----

For Netty, we need to add `h2c` as a supported protocol:

[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"]
----
@Bean
public NettyServerCustomizer serverCustomizer() {
return (server) -> server.protocol(HttpProtocol.H2C);
}
----

For Undertow, we need to enable the HTTP2 option:

[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"]
----
@Bean
public UndertowBuilderCustomizer builderCustomizer() {
return (builder) -> {
builder.setServerOption(ENABLE_HTTP2, true);
};
}
----
As of Undertow 1.4.0+, both `h2` and `h2c` are supported on JDK 8 without any additional dependencies.



Expand Down
1 change: 1 addition & 0 deletions spring-boot-project/spring-boot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {
optional("junit:junit")
optional("org.apache.commons:commons-dbcp2")
optional("org.apache.httpcomponents:httpclient")
optional("org.apache.httpcomponents.client5:httpclient5")
optional("org.apache.logging.log4j:log4j-api")
optional("org.apache.logging.log4j:log4j-core")
optional("org.apache.tomcat.embed:tomcat-embed-core")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,13 +17,16 @@
package org.springframework.boot.web.embedded.jetty;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
Expand Down Expand Up @@ -193,23 +196,26 @@ protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
}

private AbstractConnector createConnector(InetSocketAddress address, Server server) {
ServerConnector connector;
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setSendServerVersion(false);
List<ConnectionFactory> connectionFactories = new ArrayList<>();
if (getHttp2() != null && getHttp2().isEnabled()) {
connectionFactories.add(new HTTP2ServerConnectionFactory(httpConfiguration));
}
connectionFactories.add(new HttpConnectionFactory(httpConfiguration));
JettyResourceFactory resourceFactory = getResourceFactory();
ServerConnector connector;
if (resourceFactory != null) {
connector = new ServerConnector(server, resourceFactory.getExecutor(), resourceFactory.getScheduler(),
resourceFactory.getByteBufferPool(), this.acceptors, this.selectors, new HttpConnectionFactory());
resourceFactory.getByteBufferPool(), this.acceptors, this.selectors,
connectionFactories.toArray(new ConnectionFactory[0]));
}
else {
connector = new ServerConnector(server, this.acceptors, this.selectors);
connector = new ServerConnector(server, this.acceptors, this.selectors,
connectionFactories.toArray(new ConnectionFactory[0]));
}
connector.setHost(address.getHostString());
connector.setPort(address.getPort());
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) connectionFactory).getHttpConfiguration()
.setSendServerVersion(false);
}
}
return connector;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
import java.util.Set;

import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ErrorHandler;
Expand Down Expand Up @@ -175,15 +177,17 @@ private Server createServer(InetSocketAddress address) {
}

private AbstractConnector createConnector(InetSocketAddress address, Server server) {
ServerConnector connector = new ServerConnector(server, this.acceptors, this.selectors);
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setSendServerVersion(false);
List<ConnectionFactory> connectionFactories = new ArrayList<>();
if (getHttp2() != null && getHttp2().isEnabled()) {
connectionFactories.add(new HTTP2ServerConnectionFactory(httpConfiguration));
}
connectionFactories.add(new HttpConnectionFactory(httpConfiguration));
ServerConnector connector = new ServerConnector(server, this.acceptors, this.selectors,
connectionFactories.toArray(new ConnectionFactory[0]));
connector.setHost(address.getHostString());
connector.setPort(address.getPort());
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) connectionFactory).getHttpConfiguration()
.setSendServerVersion(false);
}
}
return connector;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -183,10 +183,17 @@ private HttpServer customizeSslConfiguration(HttpServer httpServer) {
}

private HttpProtocol[] listProtocols() {
if (getHttp2() != null && getHttp2().isEnabled() && getSsl() != null && getSsl().isEnabled()) {
return new HttpProtocol[] { HttpProtocol.H2, HttpProtocol.HTTP11 };
List<HttpProtocol> protocols = new ArrayList<>();
protocols.add(HttpProtocol.HTTP11);
if (getHttp2() != null && getHttp2().isEnabled()) {
if (getSsl() != null && getSsl().isEnabled()) {
protocols.add(HttpProtocol.H2);
}
else {
protocols.add(HttpProtocol.H2C);
}
}
return new HttpProtocol[] { HttpProtocol.HTTP11 };
return protocols.toArray(new HttpProtocol[0]);
}

private InetSocketAddress getListenAddress() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -190,6 +190,9 @@ protected void customizeConnector(Connector connector) {
}
// Don't bind to the socket prematurely if ApplicationContext is slow to start
connector.setProperty("bindOnInit", "false");
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(connector);
}
Expand All @@ -214,9 +217,6 @@ private void customizeProtocol(AbstractProtocol<?> protocol) {

private void customizeSsl(Connector connector) {
new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector);
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ protected void customizeConnector(Connector connector) {
}
// Don't bind to the socket prematurely if ApplicationContext is slow to start
connector.setProperty("bindOnInit", "false");
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(connector);
}
Expand All @@ -346,9 +349,6 @@ private void invokeProtocolHandlerCustomizers(ProtocolHandler protocolHandler) {

private void customizeSsl(Connector connector) {
new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector);
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -158,12 +158,12 @@ Builder createBuilder(AbstractConfigurableWebServerFactory factory) {
if (this.directBuffers != null) {
builder.setDirectBuffers(this.directBuffers);
}
Http2 http2 = factory.getHttp2();
if (http2 != null) {
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2.isEnabled());
}
if (ssl != null && ssl.isEnabled()) {
new SslBuilderCustomizer(factory.getPort(), address, ssl, factory.getSslStoreProvider()).customize(builder);
Http2 http2 = factory.getHttp2();
if (http2 != null) {
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2.isEnabled());
}
}
else {
builder.addHttpListener(port, (address != null) ? address.getHostAddress() : "0.0.0.0");
Expand Down
Loading

0 comments on commit 713c0fc

Please sign in to comment.