Skip to content

Commit 2588a71

Browse files
committed
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
1 parent 11efe92 commit 2588a71

File tree

6 files changed

+156
-39
lines changed

6 files changed

+156
-39
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
2929
import org.springframework.context.annotation.Bean;
3030
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.http.client.reactive.JettyResourceFactory;
32+
import org.springframework.http.client.reactive.ReactorResourceFactory;
3133

3234
/**
3335
* Configuration classes for reactive web servers
@@ -45,8 +47,17 @@ abstract class ReactiveWebServerFactoryConfiguration {
4547
static class EmbeddedNetty {
4648

4749
@Bean
48-
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
49-
return new NettyReactiveWebServerFactory();
50+
@ConditionalOnMissingBean
51+
public ReactorResourceFactory reactorServerResourceFactory() {
52+
return new ReactorResourceFactory();
53+
}
54+
55+
@Bean
56+
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory(
57+
ReactorResourceFactory resourceFactory) {
58+
NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory();
59+
serverFactory.setResourceFactory(resourceFactory);
60+
return serverFactory;
5061
}
5162

5263
}
@@ -69,8 +80,17 @@ public TomcatReactiveWebServerFactory tomcatReactiveWebServerFactory() {
6980
static class EmbeddedJetty {
7081

7182
@Bean
72-
public JettyReactiveWebServerFactory jettyReactiveWebServerFactory() {
73-
return new JettyReactiveWebServerFactory();
83+
@ConditionalOnMissingBean
84+
public JettyResourceFactory jettyServerResourceFactory() {
85+
return new JettyResourceFactory();
86+
}
87+
88+
@Bean
89+
public JettyReactiveWebServerFactory jettyReactiveWebServerFactory(
90+
JettyResourceFactory resourceFactory) {
91+
JettyReactiveWebServerFactory serverFactory = new JettyReactiveWebServerFactory();
92+
serverFactory.setResourceFactory(resourceFactory);
93+
return serverFactory;
7494
}
7595

7696
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static class ReactorNetty {
4646

4747
@Bean
4848
@ConditionalOnMissingBean
49-
public ReactorResourceFactory reactorResourceFactory() {
49+
public ReactorResourceFactory reactorClientResourceFactory() {
5050
return new ReactorResourceFactory();
5151
}
5252

@@ -66,7 +66,7 @@ public static class JettyClient {
6666

6767
@Bean
6868
@ConditionalOnMissingBean
69-
public JettyResourceFactory jettyResourceFactory() {
69+
public JettyResourceFactory jettyClientResourceFactory() {
7070
return new JettyResourceFactory();
7171
}
7272

spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3211,6 +3211,35 @@ instead.
32113211
There is a {github-code}/spring-boot-samples/spring-boot-sample-web-jsp[JSP sample] so
32123212
that you can see how to set things up.
32133213

3214+
[[boot-features-reactive-server]]
3215+
=== Embedded Reactive Server Support
3216+
3217+
Spring Boot includes support for the following embedded reactive web servers:
3218+
Reactor Netty, Tomcat, Jetty, and Undertow. Most developers use the appropriate “Starter”
3219+
to obtain a fully configured instance. By default, the embedded server listens for HTTP
3220+
requests on port 8080.
3221+
3222+
[[boot-features-reactive-server-resources]]
3223+
=== Reactive Server Resources Configuration
3224+
3225+
When auto-configuring a Reactor Netty or Jetty server, Spring Boot will create specific
3226+
beans that will provide HTTP resources to the server instance: `ReactorResourceFactory`
3227+
or `JettyResourceFactory`.
3228+
3229+
By default, those resources will be also shared with the Reactor Netty and Jetty clients
3230+
for optimal performances, given:
3231+
3232+
* the same technology is used for server and client
3233+
* the client instance is built using the `WebClient.Builder` bean auto-configured by
3234+
Spring Boot
3235+
3236+
Developers can override the resource configuration for Jetty and Reactor Netty by providing
3237+
a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to
3238+
both clients and servers.
3239+
3240+
You can learn more about the resource configuration on the client side in the
3241+
<<boot-features-webclient-runtime, WebClient Runtime section>>.
3242+
32143243

32153244

32163245
[[boot-features-security]]
@@ -5955,19 +5984,23 @@ The following code shows a typical example:
59555984

59565985
[[boot-features-webclient-runtime]]
59575986
=== WebClient Runtime
5958-
Spring Boot will auto-detect which `ClientHttpConnector` to drive `WebClient`, depending
5959-
on the libraries available on the application classpath.
5987+
Spring Boot will auto-detect which `ClientHttpConnector` to use to drive `WebClient`,
5988+
depending on the libraries available on the application classpath. For now, Reactor
5989+
Netty and Jetty RS client are supported.
59605990

5961-
The `spring-boot-starter-webflux` depends on `io.projectreactor.netty:reactor-netty` by
5962-
default, which brings both server and client implementations. If you choose to use Jetty
5991+
The `spring-boot-starter-webflux` starter depends on `io.projectreactor.netty:reactor-netty`
5992+
by default, which brings both server and client implementations. If you choose to use Jetty
59635993
as a reactive server instead, you should add a dependency on the Jetty Reactive HTTP
5964-
client library, `org.eclipse.jetty:jetty-reactive-httpclient`, because it will
5965-
automatically share HTTP resources with the server.
5994+
client library, `org.eclipse.jetty:jetty-reactive-httpclient`. Using the same technology
5995+
for server and client has it advantages, as it will automatically share HTTP resources
5996+
between client and server.
5997+
5998+
Developers can override the resource configuration for Jetty and Reactor Netty by providing
5999+
a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to
6000+
both clients and servers.
59666001

5967-
Developers can override this choice by defining their own `ClientHttpConnector` bean;
5968-
in this case, and depending on your HTTP client library of choice, you should also
5969-
define a resource factory bean that manages the HTTP resources for that client.
5970-
For example, a `ReactorResourceFactory` bean for the Reactor Netty client.
6002+
If you wish to override that choice for the client, you can define your own
6003+
`ClientHttpConnector` bean and have full control over the client configuration.
59716004

59726005
You can learn more about the
59736006
{spring-reference}web-reactive.html#webflux-client-builder[`WebClient` configuration

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.eclipse.jetty.server.ConnectionFactory;
2929
import org.eclipse.jetty.server.Handler;
3030
import org.eclipse.jetty.server.HttpConfiguration;
31+
import org.eclipse.jetty.server.HttpConnectionFactory;
3132
import org.eclipse.jetty.server.Server;
3233
import org.eclipse.jetty.server.ServerConnector;
3334
import org.eclipse.jetty.server.handler.HandlerWrapper;
@@ -38,6 +39,7 @@
3839
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
3940
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
4041
import org.springframework.boot.web.server.WebServer;
42+
import org.springframework.http.client.reactive.JettyResourceFactory;
4143
import org.springframework.http.server.reactive.HttpHandler;
4244
import org.springframework.http.server.reactive.JettyHttpHandlerAdapter;
4345
import org.springframework.util.Assert;
@@ -69,6 +71,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
6971

7072
private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<>();
7173

74+
private JettyResourceFactory resourceFactory;
75+
7276
private ThreadPool threadPool;
7377

7478
/**
@@ -129,11 +133,41 @@ public Collection<JettyServerCustomizer> getServerCustomizers() {
129133
return this.jettyServerCustomizers;
130134
}
131135

136+
/**
137+
* Returns a Jetty {@link ThreadPool} that should be used by the {@link Server}.
138+
* @return a Jetty {@link ThreadPool} or {@code null}
139+
*/
140+
public ThreadPool getThreadPool() {
141+
return this.threadPool;
142+
}
143+
144+
/**
145+
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
146+
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
147+
* @param threadPool a Jetty ThreadPool to be used
148+
*/
149+
public void setThreadPool(ThreadPool threadPool) {
150+
this.threadPool = threadPool;
151+
}
152+
132153
@Override
133154
public void setSelectors(int selectors) {
134155
this.selectors = selectors;
135156
}
136157

158+
/**
159+
* Set the {@link JettyResourceFactory} to get the shared resources from.
160+
* @param resourceFactory the server resources
161+
* @since 2.1.0
162+
*/
163+
public void setResourceFactory(JettyResourceFactory resourceFactory) {
164+
this.resourceFactory = resourceFactory;
165+
}
166+
167+
protected JettyResourceFactory getResourceFactory() {
168+
return this.resourceFactory;
169+
}
170+
137171
protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
138172
int port = (getPort() >= 0) ? getPort() : 0;
139173
InetSocketAddress address = new InetSocketAddress(getAddress(), port);
@@ -160,8 +194,16 @@ protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
160194
}
161195

162196
private AbstractConnector createConnector(InetSocketAddress address, Server server) {
163-
ServerConnector connector = new ServerConnector(server, this.acceptors,
164-
this.selectors);
197+
ServerConnector connector;
198+
JettyResourceFactory resourceFactory = getResourceFactory();
199+
if (resourceFactory != null) {
200+
connector = new ServerConnector(server, resourceFactory.getExecutor(),
201+
resourceFactory.getScheduler(), resourceFactory.getByteBufferPool(),
202+
this.acceptors, this.selectors, new HttpConnectionFactory());
203+
}
204+
else {
205+
connector = new ServerConnector(server, this.acceptors, this.selectors);
206+
}
165207
connector.setHost(address.getHostString());
166208
connector.setPort(address.getPort());
167209
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
@@ -195,21 +237,4 @@ private void customizeSsl(Server server, InetSocketAddress address) {
195237
.customize(server);
196238
}
197239

198-
/**
199-
* Returns a Jetty {@link ThreadPool} that should be used by the {@link Server}.
200-
* @return a Jetty {@link ThreadPool} or {@code null}
201-
*/
202-
public ThreadPool getThreadPool() {
203-
return this.threadPool;
204-
}
205-
206-
/**
207-
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
208-
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
209-
* @param threadPool a Jetty ThreadPool to be used
210-
*/
211-
public void setThreadPool(ThreadPool threadPool) {
212-
this.threadPool = threadPool;
213-
}
214-
215240
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525

2626
import reactor.netty.http.HttpProtocol;
2727
import reactor.netty.http.server.HttpServer;
28+
import reactor.netty.resources.LoopResources;
2829

2930
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
3031
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
3132
import org.springframework.boot.web.server.WebServer;
33+
import org.springframework.http.client.reactive.ReactorResourceFactory;
3234
import org.springframework.http.server.reactive.HttpHandler;
3335
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
3436
import org.springframework.util.Assert;
@@ -47,6 +49,8 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
4749

4850
private boolean useForwardHeaders;
4951

52+
private ReactorResourceFactory resourceFactory;
53+
5054
public NettyReactiveWebServerFactory() {
5155
}
5256

@@ -109,9 +113,28 @@ public void setUseForwardHeaders(boolean useForwardHeaders) {
109113
this.useForwardHeaders = useForwardHeaders;
110114
}
111115

116+
/**
117+
* Set the {@link ReactorResourceFactory} to get the shared resources from.
118+
* @param resourceFactory the server resources
119+
* @since 2.1.0
120+
*/
121+
public void setResourceFactory(ReactorResourceFactory resourceFactory) {
122+
this.resourceFactory = resourceFactory;
123+
}
124+
112125
private HttpServer createHttpServer() {
113-
HttpServer server = HttpServer.create().tcpConfiguration(
114-
(tcpServer) -> tcpServer.addressSupplier(this::getListenAddress));
126+
HttpServer server = HttpServer.create();
127+
if (this.resourceFactory != null) {
128+
LoopResources resources = this.resourceFactory.getLoopResources();
129+
Assert.notNull(resources,
130+
"No LoopResources: is ReactorResourceFactory not initialized yet?");
131+
server = server.tcpConfiguration((tcpServer) -> tcpServer.runOn(resources)
132+
.addressSupplier(this::getListenAddress));
133+
}
134+
else {
135+
server = server.tcpConfiguration(
136+
(tcpServer) -> tcpServer.addressSupplier(this::getListenAddress));
137+
}
115138
if (getSsl() != null && getSsl().isEnabled()) {
116139
SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(getSsl(),
117140
getHttp2(), getSslStoreProvider());
@@ -122,8 +145,7 @@ private HttpServer createHttpServer() {
122145
getCompression());
123146
server = compressionCustomizer.apply(server);
124147
}
125-
server = server.protocol(listProtocols());
126-
server = server.forwarded(this.useForwardHeaders);
148+
server = server.protocol(listProtocols()).forwarded(this.useForwardHeaders);
127149
return applyCustomizers(server);
128150
}
129151

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.mockito.InOrder;
2727

2828
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests;
29+
import org.springframework.http.client.reactive.JettyResourceFactory;
2930
import org.springframework.http.server.reactive.HttpHandler;
3031

3132
import static org.assertj.core.api.Assertions.assertThat;
@@ -99,4 +100,20 @@ public void useForwardedHeaders() {
99100
assertForwardHeaderIsUsed(factory);
100101
}
101102

103+
@Test
104+
public void useServerResources() throws Exception {
105+
JettyResourceFactory resourceFactory = new JettyResourceFactory();
106+
resourceFactory.afterPropertiesSet();
107+
JettyReactiveWebServerFactory factory = getFactory();
108+
factory.setResourceFactory(resourceFactory);
109+
JettyWebServer webServer = (JettyWebServer) factory
110+
.getWebServer(new EchoHandler());
111+
webServer.start();
112+
Connector connector = webServer.getServer().getConnectors()[0];
113+
assertThat(connector.getByteBufferPool())
114+
.isEqualTo(resourceFactory.getByteBufferPool());
115+
assertThat(connector.getExecutor()).isEqualTo(resourceFactory.getExecutor());
116+
assertThat(connector.getScheduler()).isEqualTo(resourceFactory.getScheduler());
117+
}
118+
102119
}

0 commit comments

Comments
 (0)