diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index dd65b54e652..6af30b33ceb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -118,6 +118,8 @@ public class ServerProperties { private final Jetty jetty = new Jetty(); + private final Netty netty = new Netty(); + private final Undertow undertow = new Undertow(); public Integer getPort() { @@ -163,10 +165,14 @@ public class ServerProperties { this.maxHttpHeaderSize = maxHttpHeaderSize; } + @Deprecated + @DeprecatedConfigurationProperty( + reason = "Each server behaves differently. Use server specific properties instead.") public Duration getConnectionTimeout() { return this.connectionTimeout; } + @Deprecated public void setConnectionTimeout(Duration connectionTimeout) { this.connectionTimeout = connectionTimeout; } @@ -203,6 +209,10 @@ public class ServerProperties { return this.jetty; } + public Netty getNetty() { + return this.netty; + } + public Undertow getUndertow() { return this.undertow; } @@ -414,6 +424,12 @@ public class ServerProperties { */ private List relaxedQueryChars = new ArrayList<>(); + /** + * Amount of time the connector will wait, after accepting a connection, for the + * request URI line to be presented. + */ + private Duration connectionTimeout; + /** * Static resource configuration. */ @@ -596,6 +612,14 @@ public class ServerProperties { this.relaxedQueryChars = relaxedQueryChars; } + public Duration getConnectionTimeout() { + return this.connectionTimeout; + } + + public void setConnectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + public Resource getResource() { return this.resource; } @@ -934,6 +958,11 @@ public class ServerProperties { */ private Duration idleTimeout = Duration.ofMillis(60000); + /** + * Time that the connection can be idle before it is closed. + */ + private Duration connectionIdleTimeout; + public Accesslog getAccesslog() { return this.accesslog; } @@ -986,6 +1015,14 @@ public class ServerProperties { return this.idleTimeout; } + public Duration getConnectionIdleTimeout() { + return this.connectionIdleTimeout; + } + + public void setConnectionIdleTimeout(Duration connectionIdleTimeout) { + this.connectionIdleTimeout = connectionIdleTimeout; + } + /** * Jetty access log properties. */ @@ -1118,6 +1155,26 @@ public class ServerProperties { } + /** + * Netty properties. + */ + public static class Netty { + + /** + * Connection timeout of the Netty channel. + */ + private Duration connectionTimeout; + + public Duration getConnectionTimeout() { + return this.connectionTimeout; + } + + public void setConnectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + } + /** * Undertow properties. */ @@ -1200,6 +1257,12 @@ public class ServerProperties { */ private boolean alwaysSetKeepAlive = true; + /** + * Amount of time a connection can sit idle without processing a request, before + * it is closed by the server. + */ + private Duration noRequestTimeout; + private final Accesslog accesslog = new Accesslog(); private final Options options = new Options(); @@ -1308,6 +1371,14 @@ public class ServerProperties { this.alwaysSetKeepAlive = alwaysSetKeepAlive; } + public Duration getNoRequestTimeout() { + return this.noRequestTimeout; + } + + public void setNoRequestTimeout(Duration noRequestTimeout) { + this.noRequestTimeout = noRequestTimeout; + } + public Accesslog getAccesslog() { return this.accesslog; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java index 7d9bf0ba36b..053e871fc3a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java @@ -89,7 +89,9 @@ public class JettyWebServerFactoryCustomizer propertyMapper.from(jettyProperties::getIdleTimeout).whenNonNull().asInt(Duration::toMillis).to( (idleTimeout) -> customizeThreadPool(factory, (threadPool) -> threadPool.setIdleTimeout(idleTimeout))); propertyMapper.from(properties::getConnectionTimeout).whenNonNull() - .to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout)); + .to((connectionTimeout) -> customizeIdleTimeout(factory, connectionTimeout)); + propertyMapper.from(jettyProperties::getConnectionIdleTimeout).whenNonNull() + .to((idleTimeout) -> customizeIdleTimeout(factory, idleTimeout)); propertyMapper.from(jettyProperties::getAccesslog).when(ServerProperties.Jetty.Accesslog::isEnabled) .to((accesslog) -> customizeAccessLog(factory, accesslog)); } @@ -106,7 +108,7 @@ public class JettyWebServerFactoryCustomizer return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE); } - private void customizeConnectionTimeout(ConfigurableJettyWebServerFactory factory, Duration connectionTimeout) { + private void customizeIdleTimeout(ConfigurableJettyWebServerFactory factory, Duration connectionTimeout) { factory.addServerCustomizers((server) -> { for (org.eclipse.jetty.server.Connector connector : server.getConnectors()) { if (connector instanceof AbstractConnector) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java index 3269ee08bc5..607f2ce1bc2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java @@ -61,6 +61,9 @@ public class NettyWebServerFactoryCustomizer propertyMapper.from(this.serverProperties::getMaxHttpHeaderSize) .to((maxHttpRequestHeaderSize) -> customizeMaxHttpHeaderSize(factory, maxHttpRequestHeaderSize)); propertyMapper.from(this.serverProperties::getConnectionTimeout) + .to((connectionTimeout) -> customizeGenericConnectionTimeout(factory, connectionTimeout)); + ServerProperties.Netty nettyProperties = this.serverProperties.getNetty(); + propertyMapper.from(nettyProperties::getConnectionTimeout).whenNonNull() .to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout)); } @@ -77,7 +80,7 @@ public class NettyWebServerFactoryCustomizer (httpRequestDecoderSpec) -> httpRequestDecoderSpec.maxHeaderSize((int) maxHttpHeaderSize.toBytes()))); } - private void customizeConnectionTimeout(NettyReactiveWebServerFactory factory, Duration connectionTimeout) { + private void customizeGenericConnectionTimeout(NettyReactiveWebServerFactory factory, Duration connectionTimeout) { if (!connectionTimeout.isZero()) { long timeoutMillis = connectionTimeout.isNegative() ? 0 : connectionTimeout.toMillis(); factory.addServerCustomizers((httpServer) -> httpServer.tcpConfiguration((tcpServer) -> tcpServer @@ -85,4 +88,9 @@ public class NettyWebServerFactoryCustomizer } } + private void customizeConnectionTimeout(NettyReactiveWebServerFactory factory, Duration connectionTimeout) { + factory.addServerCustomizers((httpServer) -> httpServer.tcpConfiguration((tcpServer) -> tcpServer + .selectorOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) connectionTimeout.toMillis()))); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java index 7f458dfafe4..8adf04dbccb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java @@ -99,6 +99,8 @@ public class TomcatWebServerFactoryCustomizer propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding); propertyMapper.from(properties::getConnectionTimeout).whenNonNull() .to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout)); + propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull() + .to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout)); propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive) .to((maxConnections) -> customizeMaxConnections(factory, maxConnections)); propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java index 06a57cd342c..c5626845782 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java @@ -101,6 +101,8 @@ public class UndertowWebServerFactoryCustomizer map.from(properties::isDecodeUrl).to(options.server(UndertowOptions.DECODE_URL)); map.from(properties::getUrlCharset).as(Charset::name).to(options.server(UndertowOptions.URL_CHARSET)); map.from(properties::isAlwaysSetKeepAlive).to(options.server(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)); + map.from(properties::getNoRequestTimeout).asInt(Duration::toMillis) + .to(options.server(UndertowOptions.NO_REQUEST_TIMEOUT)); map.from(properties.getOptions()::getServer).to(options.forEach(options::server)); map.from(properties.getOptions()::getSocket).to(options.forEach(options::socket)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java index 1a9d97964dd..0c32457d107 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java @@ -19,8 +19,11 @@ package org.springframework.boot.autoconfigure.web.embedded; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.CustomRequestLog; import org.eclipse.jetty.server.HttpConfiguration; @@ -182,6 +185,23 @@ class JettyWebServerFactoryCustomizerTests { assertThat(requestHeaderSizes).containsOnly(8192); } + @Test + void customIdleTimeout() { + bind("server.jetty.idle-timeout=60s"); + JettyWebServer server = customizeAndGetServer(); + List timeouts = connectorsIdleTimeouts(server); + assertThat(timeouts).containsOnly(60000L); + } + + private List connectorsIdleTimeouts(JettyWebServer server) { + // Start (and directly stop) server to have connectors available + server.start(); + server.stop(); + return Arrays.stream(server.getServer().getConnectors()) + .filter((connector) -> connector instanceof AbstractConnector).map(Connector::getIdleTimeout) + .collect(Collectors.toList()); + } + private List getRequestHeaderSizes(JettyWebServer server) { List requestHeaderSizes = new ArrayList<>(); // Start (and directly stop) server to have connectors available diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java index f044fca95ac..36a5a5b0cbe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java @@ -93,21 +93,29 @@ class NettyWebServerFactoryCustomizerTests { } @Test - void setConnectionTimeoutAsZero() { - setupConnectionTimeout(Duration.ZERO); + void setServerConnectionTimeoutAsZero() { + setupServerConnectionTimeout(Duration.ZERO); NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class); this.customizer.customize(factory); verifyConnectionTimeout(factory, null); } @Test - void setConnectionTimeoutAsMinusOne() { - setupConnectionTimeout(Duration.ofNanos(-1)); + void setServerConnectionTimeoutAsMinusOne() { + setupServerConnectionTimeout(Duration.ofNanos(-1)); NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class); this.customizer.customize(factory); verifyConnectionTimeout(factory, 0); } + @Test + void setServerConnectionTimeout() { + setupServerConnectionTimeout(Duration.ofSeconds(1)); + NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class); + this.customizer.customize(factory); + verifyConnectionTimeout(factory, 1000); + } + @Test void setConnectionTimeout() { setupConnectionTimeout(Duration.ofSeconds(1)); @@ -131,10 +139,16 @@ class NettyWebServerFactoryCustomizerTests { assertThat(options).containsEntry(ChannelOption.CONNECT_TIMEOUT_MILLIS, expected); } - private void setupConnectionTimeout(Duration connectionTimeout) { + private void setupServerConnectionTimeout(Duration connectionTimeout) { this.serverProperties.setUseForwardHeaders(null); this.serverProperties.setMaxHttpHeaderSize(null); this.serverProperties.setConnectionTimeout(connectionTimeout); } + private void setupConnectionTimeout(Duration connectionTimeout) { + this.serverProperties.setUseForwardHeaders(null); + this.serverProperties.setMaxHttpHeaderSize(null); + this.serverProperties.getNetty().setConnectionTimeout(connectionTimeout); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java index f15c517cb05..340f1d660d9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java @@ -288,6 +288,14 @@ class TomcatWebServerFactoryCustomizerTests { assertThat(this.serverProperties.getTomcat().getMinSpareThreads()).isEqualTo(10); } + @Test + void customConnectionTimeout() { + bind("server.tomcat.connection-timeout=30s"); + customizeAndRunServer((server) -> assertThat( + ((AbstractProtocol) server.getTomcat().getConnector().getProtocolHandler()).getConnectionTimeout()) + .isEqualTo(30000)); + } + @Test void accessLogBufferingCanBeDisabled() { bind("server.tomcat.accesslog.enabled=true", "server.tomcat.accesslog.buffered=false"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java index 4ecd23bc928..5fe8abc3ddc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java @@ -110,8 +110,8 @@ class UndertowWebServerFactoryCustomizerTests { @Test void customConnectionTimeout() { - bind("server.connectionTimeout=100"); - assertThat(boundServerOption(UndertowOptions.NO_REQUEST_TIMEOUT)).isEqualTo(100); + bind("server.undertow.no-request-timeout=1m"); + assertThat(boundServerOption(UndertowOptions.NO_REQUEST_TIMEOUT)).isEqualTo(60000); } @Test