Allow lifecycleTimeout to be set for Netty

Update `NettyReactiveWebServerFactory` and `NettyWebServer` to allow
the lifecycle (start/stop) timeout to be configured.

Fixes gh-10977
This commit is contained in:
Phillip Webb
2018-02-13 15:16:29 -08:00
parent 5c80d6dc00
commit 45347af62e
3 changed files with 49 additions and 8 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.boot.web.embedded.netty;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -42,6 +43,8 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
private List<NettyServerCustomizer> serverCustomizers = new ArrayList<>();
private Duration lifecycleTimeout;
public NettyReactiveWebServerFactory() {
}
@@ -51,10 +54,10 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
@Override
public WebServer getWebServer(HttpHandler httpHandler) {
HttpServer server = createHttpServer();
HttpServer httpServer = createHttpServer();
ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(
httpHandler);
return new NettyWebServer(server, handlerAdapter);
return new NettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout);
}
/**
@@ -86,6 +89,15 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
this.serverCustomizers.addAll(Arrays.asList(serverCustomizers));
}
/**
* Set the maximum amount of time that should be waited when starting or stopping the
* server.
* @param lifecycleTimeout the lefecycle timeout
*/
public void setLifecycleTimeout(Duration lifecycleTimeout) {
this.lifecycleTimeout = lifecycleTimeout;
}
private HttpServer createHttpServer() {
return HttpServer.builder().options((options) -> {
options.listenAddress(getListenAddress());

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.web.embedded.netty;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.time.Duration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -30,6 +31,7 @@ import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.util.Assert;
/**
* {@link WebServer} that can be used to control a Reactor Netty web server. Usually this
@@ -45,27 +47,32 @@ public class NettyWebServer implements WebServer {
private static final Log logger = LogFactory.getLog(NettyWebServer.class);
private final HttpServer httpServer;
private final ReactorHttpHandlerAdapter handlerAdapter;
private final HttpServer reactorServer;
private final Duration lifecycleTimeout;
private BlockingNettyContext nettyContext;
public NettyWebServer(HttpServer reactorServer,
ReactorHttpHandlerAdapter handlerAdapter) {
this.reactorServer = reactorServer;
public NettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter,
Duration lifecycleTimeout) {
Assert.notNull(httpServer, "HttpServer must not be null");
Assert.notNull(handlerAdapter, "HandlerAdapter must not be null");
this.httpServer = httpServer;
this.handlerAdapter = handlerAdapter;
this.lifecycleTimeout = lifecycleTimeout;
}
@Override
public void start() throws WebServerException {
if (this.nettyContext == null) {
try {
this.nettyContext = this.reactorServer.start(this.handlerAdapter);
this.nettyContext = startHttpServer();
}
catch (Exception ex) {
if (findBindException(ex) != null) {
SocketAddress address = this.reactorServer.options().getAddress();
SocketAddress address = this.httpServer.options().getAddress();
if (address instanceof InetSocketAddress) {
throw new PortInUseException(
((InetSocketAddress) address).getPort());
@@ -78,6 +85,13 @@ public class NettyWebServer implements WebServer {
}
}
private BlockingNettyContext startHttpServer() {
if (this.lifecycleTimeout != null) {
return this.httpServer.start(this.handlerAdapter, this.lifecycleTimeout);
}
return this.httpServer.start(this.handlerAdapter);
}
private BindException findBindException(Exception ex) {
Throwable candidate = ex;
while (candidate != null) {

View File

@@ -16,6 +16,7 @@
package org.springframework.boot.web.embedded.netty;
import java.time.Duration;
import java.util.Arrays;
import org.junit.Test;
@@ -25,7 +26,9 @@ import reactor.ipc.netty.http.server.HttpServerOptions;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
@@ -72,4 +75,16 @@ public class NettyReactiveWebServerFactoryTests
}
}
@Test
public void customStartupTimeout() {
Duration timeout = Duration.ofDays(365);
NettyReactiveWebServerFactory factory = getFactory();
factory.setLifecycleTimeout(timeout);
this.webServer = factory.getWebServer(new EchoHandler());
this.webServer.start();
Object context = ReflectionTestUtils.getField(this.webServer, "nettyContext");
Object actualTimeout = ReflectionTestUtils.getField(context, "lifecycleTimeout");
assertThat(actualTimeout).isEqualTo(timeout);
}
}