Skip to content

Commit

Permalink
Auto-Configure HTTP ResourceFactories on servers
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bclozel committed Oct 12, 2018
1 parent 11efe92 commit 2588a71
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
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
Expand All @@ -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;
}

}
Expand All @@ -69,8 +80,17 @@ public TomcatReactiveWebServerFactory tomcatReactiveWebServerFactory() {
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;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static class ReactorNetty {

@Bean
@ConditionalOnMissingBean
public ReactorResourceFactory reactorResourceFactory() {
public ReactorResourceFactory reactorClientResourceFactory() {
return new ReactorResourceFactory();
}

Expand All @@ -66,7 +66,7 @@ public static class JettyClient {

@Bean
@ConditionalOnMissingBean
public JettyResourceFactory jettyResourceFactory() {
public JettyResourceFactory jettyClientResourceFactory() {
return new JettyResourceFactory();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down Expand Up @@ -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 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.

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.
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand All @@ -38,6 +39,7 @@
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;
Expand Down Expand Up @@ -69,6 +71,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact

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

private JettyResourceFactory resourceFactory;

private ThreadPool threadPool;

/**
Expand Down Expand Up @@ -129,11 +133,41 @@ public Collection<JettyServerCustomizer> getServerCustomizers() {
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);
Expand All @@ -160,8 +194,16 @@ protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
}

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()) {
Expand Down Expand Up @@ -195,21 +237,4 @@ private void customizeSsl(Server server, InetSocketAddress address) {
.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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@

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;
Expand All @@ -47,6 +49,8 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact

private boolean useForwardHeaders;

private ReactorResourceFactory resourceFactory;

public NettyReactiveWebServerFactory() {
}

Expand Down Expand Up @@ -109,9 +113,28 @@ public void setUseForwardHeaders(boolean useForwardHeaders) {
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());
Expand All @@ -122,8 +145,7 @@ private HttpServer createHttpServer() {
getCompression());
server = compressionCustomizer.apply(server);
}
server = server.protocol(listProtocols());
server = server.forwarded(this.useForwardHeaders);
server = server.protocol(listProtocols()).forwarded(this.useForwardHeaders);
return applyCustomizers(server);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -99,4 +100,20 @@ public void useForwardedHeaders() {
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());
}

}

0 comments on commit 2588a71

Please sign in to comment.