Auto-Configure HTTP ResourceFactories on servers
This commit auto-configures HTTP resource factories on both Reactor Netty and Jetty server instances. This creates `ReactorResourceFactory` and `JettyResourceFactory` beans when necessary - those beans can be reused and applied by the client auto-configuration in order to share resources between client and server for optimal performance. The server auto-configuration has the highest precedence, so from now on, the auto-configured ResourceFactory bean on the client side will be skipped if a reactive server is configured. Closes gh-14495
This commit is contained in:
@@ -28,6 +28,8 @@ import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerF
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.reactive.JettyResourceFactory;
|
||||
import org.springframework.http.client.reactive.ReactorResourceFactory;
|
||||
|
||||
/**
|
||||
* Configuration classes for reactive web servers
|
||||
@@ -45,8 +47,17 @@ abstract class ReactiveWebServerFactoryConfiguration {
|
||||
static class EmbeddedNetty {
|
||||
|
||||
@Bean
|
||||
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
|
||||
return new NettyReactiveWebServerFactory();
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorResourceFactory reactorServerResourceFactory() {
|
||||
return new ReactorResourceFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory(
|
||||
ReactorResourceFactory resourceFactory) {
|
||||
NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory();
|
||||
serverFactory.setResourceFactory(resourceFactory);
|
||||
return serverFactory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -69,8 +80,17 @@ abstract class ReactiveWebServerFactoryConfiguration {
|
||||
static class EmbeddedJetty {
|
||||
|
||||
@Bean
|
||||
public JettyReactiveWebServerFactory jettyReactiveWebServerFactory() {
|
||||
return new JettyReactiveWebServerFactory();
|
||||
@ConditionalOnMissingBean
|
||||
public JettyResourceFactory jettyServerResourceFactory() {
|
||||
return new JettyResourceFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JettyReactiveWebServerFactory jettyReactiveWebServerFactory(
|
||||
JettyResourceFactory resourceFactory) {
|
||||
JettyReactiveWebServerFactory serverFactory = new JettyReactiveWebServerFactory();
|
||||
serverFactory.setResourceFactory(resourceFactory);
|
||||
return serverFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class ClientHttpConnectorConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorResourceFactory reactorResourceFactory() {
|
||||
public ReactorResourceFactory reactorClientResourceFactory() {
|
||||
return new ReactorResourceFactory();
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class ClientHttpConnectorConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public JettyResourceFactory jettyResourceFactory() {
|
||||
public JettyResourceFactory jettyClientResourceFactory() {
|
||||
return new JettyResourceFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -3211,6 +3211,35 @@ instead.
|
||||
There is a {github-code}/spring-boot-samples/spring-boot-sample-web-jsp[JSP sample] so
|
||||
that you can see how to set things up.
|
||||
|
||||
[[boot-features-reactive-server]]
|
||||
=== Embedded Reactive Server Support
|
||||
|
||||
Spring Boot includes support for the following embedded reactive web servers:
|
||||
Reactor Netty, Tomcat, Jetty, and Undertow. Most developers use the appropriate “Starter”
|
||||
to obtain a fully configured instance. By default, the embedded server listens for HTTP
|
||||
requests on port 8080.
|
||||
|
||||
[[boot-features-reactive-server-resources]]
|
||||
=== Reactive Server Resources Configuration
|
||||
|
||||
When auto-configuring a Reactor Netty or Jetty server, Spring Boot will create specific
|
||||
beans that will provide HTTP resources to the server instance: `ReactorResourceFactory`
|
||||
or `JettyResourceFactory`.
|
||||
|
||||
By default, those resources will be also shared with the Reactor Netty and Jetty clients
|
||||
for optimal performances, given:
|
||||
|
||||
* the same technology is used for server and client
|
||||
* the client instance is built using the `WebClient.Builder` bean auto-configured by
|
||||
Spring Boot
|
||||
|
||||
Developers can override the resource configuration for Jetty and Reactor Netty by providing
|
||||
a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to
|
||||
both clients and servers.
|
||||
|
||||
You can learn more about the resource configuration on the client side in the
|
||||
<<boot-features-webclient-runtime, WebClient Runtime section>>.
|
||||
|
||||
|
||||
|
||||
[[boot-features-security]]
|
||||
@@ -5955,19 +5984,23 @@ The following code shows a typical example:
|
||||
|
||||
[[boot-features-webclient-runtime]]
|
||||
=== WebClient Runtime
|
||||
Spring Boot will auto-detect which `ClientHttpConnector` to drive `WebClient`, depending
|
||||
on the libraries available on the application classpath.
|
||||
Spring Boot will auto-detect which `ClientHttpConnector` to use to drive `WebClient`,
|
||||
depending on the libraries available on the application classpath. For now, Reactor
|
||||
Netty and Jetty RS client are supported.
|
||||
|
||||
The `spring-boot-starter-webflux` depends on `io.projectreactor.netty:reactor-netty` by
|
||||
default, which brings both server and client implementations. If you choose to use Jetty
|
||||
The `spring-boot-starter-webflux` starter depends on `io.projectreactor.netty:reactor-netty`
|
||||
by default, which brings both server and client implementations. If you choose to use Jetty
|
||||
as a reactive server instead, you should add a dependency on the Jetty Reactive HTTP
|
||||
client library, `org.eclipse.jetty:jetty-reactive-httpclient`, because it will
|
||||
automatically share HTTP resources with the server.
|
||||
client library, `org.eclipse.jetty:jetty-reactive-httpclient`. Using the same technology
|
||||
for server and client has it advantages, as it will automatically share HTTP resources
|
||||
between client and server.
|
||||
|
||||
Developers can override this choice by defining their own `ClientHttpConnector` bean;
|
||||
in this case, and depending on your HTTP client library of choice, you should also
|
||||
define a resource factory bean that manages the HTTP resources for that client.
|
||||
For example, a `ReactorResourceFactory` bean for the Reactor Netty client.
|
||||
Developers can override the resource configuration for Jetty and Reactor Netty by providing
|
||||
a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to
|
||||
both clients and servers.
|
||||
|
||||
If you wish to override that choice for the client, you can define your own
|
||||
`ClientHttpConnector` bean and have full control over the client configuration.
|
||||
|
||||
You can learn more about the
|
||||
{spring-reference}web-reactive.html#webflux-client-builder[`WebClient` configuration
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.eclipse.jetty.server.AbstractConnector;
|
||||
import org.eclipse.jetty.server.ConnectionFactory;
|
||||
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.HandlerWrapper;
|
||||
@@ -38,6 +39,7 @@ import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.client.reactive.JettyResourceFactory;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.JettyHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -69,6 +71,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
|
||||
private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<>();
|
||||
|
||||
private JettyResourceFactory resourceFactory;
|
||||
|
||||
private ThreadPool threadPool;
|
||||
|
||||
/**
|
||||
@@ -129,11 +133,41 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
return this.jettyServerCustomizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Jetty {@link ThreadPool} that should be used by the {@link Server}.
|
||||
* @return a Jetty {@link ThreadPool} or {@code null}
|
||||
*/
|
||||
public ThreadPool getThreadPool() {
|
||||
return this.threadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
|
||||
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
|
||||
* @param threadPool a Jetty ThreadPool to be used
|
||||
*/
|
||||
public void setThreadPool(ThreadPool threadPool) {
|
||||
this.threadPool = threadPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectors(int selectors) {
|
||||
this.selectors = selectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link JettyResourceFactory} to get the shared resources from.
|
||||
* @param resourceFactory the server resources
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public void setResourceFactory(JettyResourceFactory resourceFactory) {
|
||||
this.resourceFactory = resourceFactory;
|
||||
}
|
||||
|
||||
protected JettyResourceFactory getResourceFactory() {
|
||||
return this.resourceFactory;
|
||||
}
|
||||
|
||||
protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
|
||||
int port = (getPort() >= 0) ? getPort() : 0;
|
||||
InetSocketAddress address = new InetSocketAddress(getAddress(), port);
|
||||
@@ -160,8 +194,16 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
}
|
||||
|
||||
private AbstractConnector createConnector(InetSocketAddress address, Server server) {
|
||||
ServerConnector connector = new ServerConnector(server, this.acceptors,
|
||||
this.selectors);
|
||||
ServerConnector connector;
|
||||
JettyResourceFactory resourceFactory = getResourceFactory();
|
||||
if (resourceFactory != null) {
|
||||
connector = new ServerConnector(server, resourceFactory.getExecutor(),
|
||||
resourceFactory.getScheduler(), resourceFactory.getByteBufferPool(),
|
||||
this.acceptors, this.selectors, new HttpConnectionFactory());
|
||||
}
|
||||
else {
|
||||
connector = new ServerConnector(server, this.acceptors, this.selectors);
|
||||
}
|
||||
connector.setHost(address.getHostString());
|
||||
connector.setPort(address.getPort());
|
||||
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
|
||||
@@ -195,21 +237,4 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
.customize(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Jetty {@link ThreadPool} that should be used by the {@link Server}.
|
||||
* @return a Jetty {@link ThreadPool} or {@code null}
|
||||
*/
|
||||
public ThreadPool getThreadPool() {
|
||||
return this.threadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
|
||||
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
|
||||
* @param threadPool a Jetty ThreadPool to be used
|
||||
*/
|
||||
public void setThreadPool(ThreadPool threadPool) {
|
||||
this.threadPool = threadPool;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,10 +25,12 @@ import java.util.List;
|
||||
|
||||
import reactor.netty.http.HttpProtocol;
|
||||
import reactor.netty.http.server.HttpServer;
|
||||
import reactor.netty.resources.LoopResources;
|
||||
|
||||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.client.reactive.ReactorResourceFactory;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -47,6 +49,8 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
|
||||
private boolean useForwardHeaders;
|
||||
|
||||
private ReactorResourceFactory resourceFactory;
|
||||
|
||||
public NettyReactiveWebServerFactory() {
|
||||
}
|
||||
|
||||
@@ -109,9 +113,28 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ReactorResourceFactory} to get the shared resources from.
|
||||
* @param resourceFactory the server resources
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public void setResourceFactory(ReactorResourceFactory resourceFactory) {
|
||||
this.resourceFactory = resourceFactory;
|
||||
}
|
||||
|
||||
private HttpServer createHttpServer() {
|
||||
HttpServer server = HttpServer.create().tcpConfiguration(
|
||||
(tcpServer) -> tcpServer.addressSupplier(this::getListenAddress));
|
||||
HttpServer server = HttpServer.create();
|
||||
if (this.resourceFactory != null) {
|
||||
LoopResources resources = this.resourceFactory.getLoopResources();
|
||||
Assert.notNull(resources,
|
||||
"No LoopResources: is ReactorResourceFactory not initialized yet?");
|
||||
server = server.tcpConfiguration((tcpServer) -> tcpServer.runOn(resources)
|
||||
.addressSupplier(this::getListenAddress));
|
||||
}
|
||||
else {
|
||||
server = server.tcpConfiguration(
|
||||
(tcpServer) -> tcpServer.addressSupplier(this::getListenAddress));
|
||||
}
|
||||
if (getSsl() != null && getSsl().isEnabled()) {
|
||||
SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(getSsl(),
|
||||
getHttp2(), getSslStoreProvider());
|
||||
@@ -122,8 +145,7 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
getCompression());
|
||||
server = compressionCustomizer.apply(server);
|
||||
}
|
||||
server = server.protocol(listProtocols());
|
||||
server = server.forwarded(this.useForwardHeaders);
|
||||
server = server.protocol(listProtocols()).forwarded(this.useForwardHeaders);
|
||||
return applyCustomizers(server);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests;
|
||||
import org.springframework.http.client.reactive.JettyResourceFactory;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -99,4 +100,20 @@ public class JettyReactiveWebServerFactoryTests
|
||||
assertForwardHeaderIsUsed(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useServerResources() throws Exception {
|
||||
JettyResourceFactory resourceFactory = new JettyResourceFactory();
|
||||
resourceFactory.afterPropertiesSet();
|
||||
JettyReactiveWebServerFactory factory = getFactory();
|
||||
factory.setResourceFactory(resourceFactory);
|
||||
JettyWebServer webServer = (JettyWebServer) factory
|
||||
.getWebServer(new EchoHandler());
|
||||
webServer.start();
|
||||
Connector connector = webServer.getServer().getConnectors()[0];
|
||||
assertThat(connector.getByteBufferPool())
|
||||
.isEqualTo(resourceFactory.getByteBufferPool());
|
||||
assertThat(connector.getExecutor()).isEqualTo(resourceFactory.getExecutor());
|
||||
assertThat(connector.getScheduler()).isEqualTo(resourceFactory.getScheduler());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user