diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java index ec18fee859..6e8d9383fd 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java @@ -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 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()); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java index 511ed67cc7..8e733821be 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java @@ -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) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java index 1ba33ec06f..31b0bf10d0 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java @@ -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); + } + }