Defer accessing loop resources until web server start

Closes gh-37209
pull/37434/head
Andy Wilkinson 1 year ago
parent 0fc97e9315
commit 6094212217

@ -27,7 +27,6 @@ import java.util.Set;
import reactor.netty.http.HttpProtocol; import reactor.netty.http.HttpProtocol;
import reactor.netty.http.server.HttpServer; 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.AbstractReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
@ -78,7 +77,7 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
NettyWebServer createNettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, NettyWebServer createNettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter,
Duration lifecycleTimeout, Shutdown shutdown) { Duration lifecycleTimeout, Shutdown shutdown) {
return new NettyWebServer(httpServer, handlerAdapter, lifecycleTimeout, shutdown); return new NettyWebServer(httpServer, handlerAdapter, lifecycleTimeout, shutdown, this.resourceFactory);
} }
/** /**
@ -158,15 +157,7 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
} }
private HttpServer createHttpServer() { private HttpServer createHttpServer() {
HttpServer server = HttpServer.create(); HttpServer server = HttpServer.create().bindAddress(this::getListenAddress);
if (this.resourceFactory != null) {
LoopResources resources = this.resourceFactory.getLoopResources();
Assert.notNull(resources, "No LoopResources: is ReactorResourceFactory not initialized yet?");
server = server.runOn(resources).bindAddress(this::getListenAddress);
}
else {
server = server.bindAddress(this::getListenAddress);
}
if (Ssl.isEnabled(getSsl())) { if (Ssl.isEnabled(getSsl())) {
server = customizeSslConfiguration(server); server = customizeSslConfiguration(server);
} }

@ -35,6 +35,7 @@ import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.HttpServerRequest; import reactor.netty.http.server.HttpServerRequest;
import reactor.netty.http.server.HttpServerResponse; import reactor.netty.http.server.HttpServerResponse;
import reactor.netty.http.server.HttpServerRoutes; import reactor.netty.http.server.HttpServerRoutes;
import reactor.netty.resources.LoopResources;
import org.springframework.boot.web.server.GracefulShutdownCallback; import org.springframework.boot.web.server.GracefulShutdownCallback;
import org.springframework.boot.web.server.GracefulShutdownResult; import org.springframework.boot.web.server.GracefulShutdownResult;
@ -42,6 +43,7 @@ import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.Shutdown; import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.server.WebServerException;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -74,12 +76,40 @@ public class NettyWebServer implements WebServer {
private final GracefulShutdown gracefulShutdown; private final GracefulShutdown gracefulShutdown;
private final ReactorResourceFactory resourceFactory;
private List<NettyRouteProvider> routeProviders = Collections.emptyList(); private List<NettyRouteProvider> routeProviders = Collections.emptyList();
private volatile DisposableServer disposableServer; private volatile DisposableServer disposableServer;
/**
* Creates a new {@code NettyWebServer} instance.
* @param httpServer the HTTP server
* @param handlerAdapter the handler adapter
* @param lifecycleTimeout the lifecycle timeout, may be {@code null}
* @param shutdown the shutdown, may be {@code null}
* @deprecated since 3.2.0 for removal in 3.4.0 in favor of
* {@link #NettyWebServer(HttpServer, ReactorHttpHandlerAdapter, Duration, Shutdown, ReactorResourceFactory)}
*/
@Deprecated(since = "3.2.0", forRemoval = true)
public NettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, Duration lifecycleTimeout, public NettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, Duration lifecycleTimeout,
Shutdown shutdown) { Shutdown shutdown) {
this(httpServer, handlerAdapter, lifecycleTimeout, shutdown, null);
}
/**
* Creates a new {@code NettyWebServer} instance.
* @param httpServer the HTTP server
* @param handlerAdapter the handler adapter
* @param lifecycleTimeout the lifecycle timeout, may be {@code null}
* @param shutdown the shutdown, may be {@code null}
* @param resourceFactory the factory for the server's {@link LoopResources loop
* resources}, may be {@code null}
* @since 3.2.0
* {@link #NettyWebServer(HttpServer, ReactorHttpHandlerAdapter, Duration, Shutdown, ReactorResourceFactory)}
*/
public NettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, Duration lifecycleTimeout,
Shutdown shutdown, ReactorResourceFactory resourceFactory) {
Assert.notNull(httpServer, "HttpServer must not be null"); Assert.notNull(httpServer, "HttpServer must not be null");
Assert.notNull(handlerAdapter, "HandlerAdapter must not be null"); Assert.notNull(handlerAdapter, "HandlerAdapter must not be null");
this.lifecycleTimeout = lifecycleTimeout; this.lifecycleTimeout = lifecycleTimeout;
@ -87,6 +117,7 @@ public class NettyWebServer implements WebServer {
this.httpServer = httpServer.channelGroup(new DefaultChannelGroup(new DefaultEventExecutor())); this.httpServer = httpServer.channelGroup(new DefaultChannelGroup(new DefaultEventExecutor()));
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(() -> this.disposableServer) this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(() -> this.disposableServer)
: null; : null;
this.resourceFactory = resourceFactory;
} }
public void setRouteProviders(List<NettyRouteProvider> routeProviders) { public void setRouteProviders(List<NettyRouteProvider> routeProviders) {
@ -143,6 +174,11 @@ public class NettyWebServer implements WebServer {
else { else {
server = server.route(this::applyRouteProviders); server = server.route(this::applyRouteProviders);
} }
if (this.resourceFactory != null) {
LoopResources resources = this.resourceFactory.getLoopResources();
Assert.notNull(resources, "No LoopResources: is ReactorResourceFactory not initialized yet?");
server = server.runOn(resources);
}
if (this.lifecycleTimeout != null) { if (this.lifecycleTimeout != null) {
return server.bindNow(this.lifecycleTimeout); return server.bindNow(this.lifecycleTimeout);
} }

@ -41,12 +41,14 @@ import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.Ssl;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
@ -80,6 +82,23 @@ class NettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactor
assertThat(this.webServer.getPort()).isEqualTo(-1); assertThat(this.webServer.getPort()).isEqualTo(-1);
} }
@Test
void resourceFactoryAndWebServerLifecycle() {
NettyReactiveWebServerFactory factory = getFactory();
factory.setPort(0);
ReactorResourceFactory resourceFactory = new ReactorResourceFactory();
factory.setResourceFactory(resourceFactory);
this.webServer = factory.getWebServer(new EchoHandler());
assertThatNoException().isThrownBy(() -> {
resourceFactory.start();
this.webServer.start();
this.webServer.stop();
resourceFactory.stop();
resourceFactory.start();
this.webServer.start();
});
}
private void portMatchesRequirement(PortInUseException exception) { private void portMatchesRequirement(PortInUseException exception) {
assertThat(exception.getPort()).isEqualTo(this.webServer.getPort()); assertThat(exception.getPort()).isEqualTo(this.webServer.getPort());
} }
@ -199,7 +218,7 @@ class NettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactor
NoPortNettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, Duration lifecycleTimeout, NoPortNettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, Duration lifecycleTimeout,
Shutdown shutdown) { Shutdown shutdown) {
super(httpServer, handlerAdapter, lifecycleTimeout, shutdown); super(httpServer, handlerAdapter, lifecycleTimeout, shutdown, null);
} }
@Override @Override

Loading…
Cancel
Save