Commit 713c0fce authored by Andy Wilkinson's avatar Andy Wilkinson

Auto-configure H2C when HTTP/2 is enabled and SSL is disabled

parent 288e86d8
...@@ -594,26 +594,23 @@ We recommend using `application.properties` to configure HTTPS, as the HTTP conn ...@@ -594,26 +594,23 @@ We recommend using `application.properties` to configure HTTPS, as the HTTP conn
[[howto-configure-http2]] [[howto-configure-http2]]
=== Configure HTTP/2 === Configure HTTP/2
You can enable HTTP/2 support in your Spring Boot application with the configprop:server.http2.enabled[] configuration property. 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]] [[howto-configure-http2-tomcat]]
==== HTTP/2 with 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. 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, HTTP/2 can be used on JDK 8 if the `libtcnative` library and its dependencies are installed on the host operating system. 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. 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`. 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]. 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"] [indent=0,subs="attributes"]
---- ----
...@@ -626,7 +623,8 @@ This error is not fatal, and the application still starts with HTTP/1.1 SSL supp ...@@ -626,7 +623,8 @@ This error is not fatal, and the application still starts with HTTP/1.1 SSL supp
[[howto-configure-http2-jetty]] [[howto-configure-http2-jetty]]
==== HTTP/2 with Jetty ==== HTTP/2 with Jetty
For HTTP/2 support, Jetty requires the additional `org.eclipse.jetty.http2:http2-server` dependency. 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-java-server` for applications running on JDK9+
* `org.eclipse.jetty:jetty-alpn-openjdk8-server` for applications running on JDK8u252+ * `org.eclipse.jetty:jetty-alpn-openjdk8-server` for applications running on JDK8u252+
...@@ -637,8 +635,9 @@ Now depending on your deployment, you also need to choose other dependencies: ...@@ -637,8 +635,9 @@ Now depending on your deployment, you also need to choose other dependencies:
[[howto-configure-http2-netty]] [[howto-configure-http2-netty]]
==== HTTP/2 with Reactor Netty ==== HTTP/2 with Reactor Netty
The `spring-boot-webflux-starter` is using by default Reactor Netty as a server. 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. Reactor Netty supports `h2c` using JDK 8 or later with no additional dependencies.
For JDK 8 environments, or for optimal runtime performance, this server also supports HTTP/2 with native libraries. 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. 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. Spring Boot manages the version for the `io.netty:netty-tcnative-boringssl-static` "uber jar", containing native libraries for all platforms.
...@@ -648,65 +647,7 @@ Developers can choose to import only the required dependencies using a classifie ...@@ -648,65 +647,7 @@ Developers can choose to import only the required dependencies using a classifie
[[howto-configure-http2-undertow]] [[howto-configure-http2-undertow]]
==== HTTP/2 with Undertow ==== HTTP/2 with Undertow
As of Undertow 1.4.0+, HTTP/2 is supported without any additional requirement on JDK8. As of Undertow 1.4.0+, both `h2` and `h2c` are supported on JDK 8 without any additional dependencies.
[[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);
};
}
----
......
...@@ -47,6 +47,7 @@ dependencies { ...@@ -47,6 +47,7 @@ dependencies {
optional("junit:junit") optional("junit:junit")
optional("org.apache.commons:commons-dbcp2") optional("org.apache.commons:commons-dbcp2")
optional("org.apache.httpcomponents:httpclient") optional("org.apache.httpcomponents:httpclient")
optional("org.apache.httpcomponents.client5:httpclient5")
optional("org.apache.logging.log4j:log4j-api") optional("org.apache.logging.log4j:log4j-api")
optional("org.apache.logging.log4j:log4j-core") optional("org.apache.logging.log4j:log4j-core")
optional("org.apache.tomcat.embed:tomcat-embed-core") optional("org.apache.tomcat.embed:tomcat-embed-core")
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -17,13 +17,16 @@ ...@@ -17,13 +17,16 @@
package org.springframework.boot.web.embedded.jetty; package org.springframework.boot.web.embedded.jetty;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
...@@ -193,23 +196,26 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact ...@@ -193,23 +196,26 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
} }
private AbstractConnector createConnector(InetSocketAddress address, Server server) { 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(); JettyResourceFactory resourceFactory = getResourceFactory();
ServerConnector connector;
if (resourceFactory != null) { if (resourceFactory != null) {
connector = new ServerConnector(server, resourceFactory.getExecutor(), resourceFactory.getScheduler(), 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 { 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.setHost(address.getHostString());
connector.setPort(address.getPort()); connector.setPort(address.getPort());
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) connectionFactory).getHttpConfiguration()
.setSendServerVersion(false);
}
}
return connector; return connector;
} }
......
...@@ -34,11 +34,13 @@ import java.util.List; ...@@ -34,11 +34,13 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.server.handler.ErrorHandler;
...@@ -175,15 +177,17 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor ...@@ -175,15 +177,17 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
} }
private AbstractConnector createConnector(InetSocketAddress address, Server server) { 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.setHost(address.getHostString());
connector.setPort(address.getPort()); connector.setPort(address.getPort());
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) connectionFactory).getHttpConfiguration()
.setSendServerVersion(false);
}
}
return connector; return connector;
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -183,10 +183,17 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact ...@@ -183,10 +183,17 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
} }
private HttpProtocol[] listProtocols() { private HttpProtocol[] listProtocols() {
if (getHttp2() != null && getHttp2().isEnabled() && getSsl() != null && getSsl().isEnabled()) { List<HttpProtocol> protocols = new ArrayList<>();
return new HttpProtocol[] { HttpProtocol.H2, HttpProtocol.HTTP11 }; 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() { private InetSocketAddress getListenAddress() {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -190,6 +190,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -190,6 +190,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
} }
// Don't bind to the socket prematurely if ApplicationContext is slow to start // Don't bind to the socket prematurely if ApplicationContext is slow to start
connector.setProperty("bindOnInit", "false"); connector.setProperty("bindOnInit", "false");
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
if (getSsl() != null && getSsl().isEnabled()) { if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(connector); customizeSsl(connector);
} }
...@@ -214,9 +217,6 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -214,9 +217,6 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
private void customizeSsl(Connector connector) { private void customizeSsl(Connector connector) {
new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector); new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector);
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
} }
@Override @Override
......
...@@ -322,6 +322,9 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -322,6 +322,9 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
} }
// Don't bind to the socket prematurely if ApplicationContext is slow to start // Don't bind to the socket prematurely if ApplicationContext is slow to start
connector.setProperty("bindOnInit", "false"); connector.setProperty("bindOnInit", "false");
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
if (getSsl() != null && getSsl().isEnabled()) { if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(connector); customizeSsl(connector);
} }
...@@ -346,9 +349,6 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -346,9 +349,6 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
private void customizeSsl(Connector connector) { private void customizeSsl(Connector connector) {
new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector); new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector);
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
} }
/** /**
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -158,12 +158,12 @@ class UndertowWebServerFactoryDelegate { ...@@ -158,12 +158,12 @@ class UndertowWebServerFactoryDelegate {
if (this.directBuffers != null) { if (this.directBuffers != null) {
builder.setDirectBuffers(this.directBuffers); builder.setDirectBuffers(this.directBuffers);
} }
Http2 http2 = factory.getHttp2();
if (http2 != null) {
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2.isEnabled());
}
if (ssl != null && ssl.isEnabled()) { if (ssl != null && ssl.isEnabled()) {
new SslBuilderCustomizer(factory.getPort(), address, ssl, factory.getSslStoreProvider()).customize(builder); 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 { else {
builder.addHttpListener(port, (address != null) ? address.getHostAddress() : "0.0.0.0"); builder.addHttpListener(port, (address != null) ? address.getHostAddress() : "0.0.0.0");
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.web.reactive.server; package org.springframework.boot.web.reactive.server;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
...@@ -28,6 +29,7 @@ import java.util.concurrent.BlockingQueue; ...@@ -28,6 +29,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
...@@ -39,6 +41,13 @@ import io.netty.handler.codec.http.HttpHeaderNames; ...@@ -39,6 +41,13 @@ import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
...@@ -52,6 +61,7 @@ import reactor.test.StepVerifier; ...@@ -52,6 +61,7 @@ import reactor.test.StepVerifier;
import org.springframework.boot.web.server.Compression; import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.GracefulShutdownResult; import org.springframework.boot.web.server.GracefulShutdownResult;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Shutdown; import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
...@@ -448,6 +458,39 @@ public abstract class AbstractReactiveWebServerFactoryTests { ...@@ -448,6 +458,39 @@ public abstract class AbstractReactiveWebServerFactoryTests {
blockingHandler.completeOne(); blockingHandler.completeOne();
} }
@Test
void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed()
throws InterruptedException, ExecutionException, IOException {
AbstractReactiveWebServerFactory factory = getFactory();
Http2 http2 = new Http2();
http2.setEnabled(true);
factory.setHttp2(http2);
this.webServer = factory.getWebServer(new EchoHandler());
this.webServer.start();
try (CloseableHttpAsyncClient http2Client = HttpAsyncClients.createHttp2Default()) {
http2Client.start();
SimpleHttpRequest request = SimpleHttpRequests.post("http://localhost:" + this.webServer.getPort());
request.setBody("Hello World", ContentType.TEXT_PLAIN);
SimpleHttpResponse response = http2Client.execute(request, new FutureCallback<SimpleHttpResponse>() {
@Override
public void failed(Exception ex) {
}
@Override
public void completed(SimpleHttpResponse result) {
}
@Override
public void cancelled() {
}
}).get();
assertThat(response.getCode() == HttpStatus.OK.value());
assertThat(response.getBodyText()).isEqualTo("Hello World");
}
}
protected WebClient prepareCompressionTest() { protected WebClient prepareCompressionTest() {
Compression compression = new Compression(); Compression compression = new Compression();
compression.setEnabled(true); compression.setEnabled(true);
......
...@@ -49,6 +49,7 @@ import java.util.concurrent.BlockingQueue; ...@@ -49,6 +49,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier; import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture; import java.util.concurrent.RunnableFuture;
...@@ -78,6 +79,12 @@ import javax.servlet.http.HttpServletResponse; ...@@ -78,6 +79,12 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory; import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.InputStreamFactory; import org.apache.http.client.entity.InputStreamFactory;
...@@ -112,6 +119,7 @@ import org.springframework.boot.testsupport.web.servlet.ExampleServlet; ...@@ -112,6 +119,7 @@ import org.springframework.boot.testsupport.web.servlet.ExampleServlet;
import org.springframework.boot.web.server.Compression; import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.GracefulShutdownResult; import org.springframework.boot.web.server.GracefulShutdownResult;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.server.MimeMappings;
import org.springframework.boot.web.server.PortInUseException; import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.Shutdown; import org.springframework.boot.web.server.Shutdown;
...@@ -1121,6 +1129,39 @@ public abstract class AbstractServletWebServerFactoryTests { ...@@ -1121,6 +1129,39 @@ public abstract class AbstractServletWebServerFactoryTests {
} }
} }
@Test
void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed()
throws InterruptedException, ExecutionException, IOException {
AbstractServletWebServerFactory factory = getFactory();
Http2 http2 = new Http2();
http2.setEnabled(true);
factory.setHttp2(http2);
this.webServer = factory.getWebServer(exampleServletRegistration());
this.webServer.start();
try (CloseableHttpAsyncClient http2Client = HttpAsyncClients.createHttp2Default()) {
http2Client.start();
SimpleHttpRequest request = SimpleHttpRequests
.get("http://localhost:" + this.webServer.getPort() + "/hello");
SimpleHttpResponse response = http2Client.execute(request, new FutureCallback<SimpleHttpResponse>() {
@Override
public void failed(Exception ex) {
}
@Override
public void completed(SimpleHttpResponse result) {
}
@Override
public void cancelled() {
}
}).get();
assertThat(response.getCode()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getBodyText()).isEqualTo("Hello World");
}
}
protected Future<Object> initiateGetRequest(int port, String path) { protected Future<Object> initiateGetRequest(int port, String path) {
return initiateGetRequest(HttpClients.createMinimal(), port, path); return initiateGetRequest(HttpClients.createMinimal(), port, path);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment