Deprecate server.connection-timeout property

Prior to this commit, all supported servers would share the same
configuration property `server.connection-timeout`. Unfortunately, in
many cases the behavior of this timeout changes depending on the server.
From actual connection setup timeout, to detecting and closing idle
connections, this property cannot be properly translated from one server
implementation to another.

This commit deprecates this configuration property and introduces server
specific properties:

* `server.jetty.connection-idle-timeout`
(Time that the connection can be idle before it is closed.)
* `server.netty.connection-timeout`
(Connection timeout of the Netty channel.)
* `server.tomcat.connection-timeout`
(Amount of time the connector will wait, after accepting a connection,
for the request URI line to be presented.)
* `server.undertow.no-request-timeout`
(Amount of time a connection can sit idle without processing a request,
before it is closed by the server.)

`server.connection-timeout` is now deprecated and will be removed in a
future release.

Fixes gh-18473
This commit is contained in:
Brian Clozel 2019-10-16 11:36:21 +02:00
parent 4eaa3873dd
commit e4fa9ce8c6
10 changed files with 143 additions and 12 deletions

View File

@ -111,6 +111,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() {
@ -153,10 +155,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;
}
@ -193,6 +199,10 @@ public class ServerProperties {
return this.jetty;
}
public Netty getNetty() {
return this.netty;
}
public Undertow getUndertow() {
return this.undertow;
}
@ -376,6 +386,12 @@ public class ServerProperties {
*/
private List<String> additionalTldSkipPatterns = 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.
*/
@ -532,6 +548,14 @@ public class ServerProperties {
this.additionalTldSkipPatterns = additionalTldSkipPatterns;
}
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public Resource getResource() {
return this.resource;
}
@ -738,6 +762,11 @@ public class ServerProperties {
*/
private Integer selectors = -1;
/**
* Time that the connection can be idle before it is closed.
*/
private Duration connectionIdleTimeout;
public Accesslog getAccesslog() {
return this.accesslog;
}
@ -766,6 +795,14 @@ public class ServerProperties {
this.selectors = selectors;
}
public Duration getConnectionIdleTimeout() {
return this.connectionIdleTimeout;
}
public void setConnectionIdleTimeout(Duration connectionIdleTimeout) {
this.connectionIdleTimeout = connectionIdleTimeout;
}
/**
* Jetty access log properties.
*/
@ -931,6 +968,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.
*/
@ -970,6 +1027,12 @@ public class ServerProperties {
*/
private boolean eagerFilterInit = 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();
public DataSize getMaxHttpPostSize() {
@ -1020,6 +1083,14 @@ public class ServerProperties {
this.eagerFilterInit = eagerFilterInit;
}
public Duration getNoRequestTimeout() {
return this.noRequestTimeout;
}
public void setNoRequestTimeout(Duration noRequestTimeout) {
this.noRequestTimeout = noRequestTimeout;
}
public Accesslog getAccesslog() {
return this.accesslog;
}

View File

@ -77,7 +77,9 @@ public class JettyWebServerFactoryCustomizer
propertyMapper.from(jettyProperties::getMaxHttpPostSize).asInt(DataSize::toBytes).when(this::isPositive)
.to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory, maxHttpPostSize));
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));
}
@ -94,7 +96,7 @@ public class JettyWebServerFactoryCustomizer
return platform != null && platform.isUsingForwardHeaders();
}
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) {

View File

@ -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())));
}
}

View File

@ -94,6 +94,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)

View File

@ -81,14 +81,16 @@ public class UndertowWebServerFactoryCustomizer
propertyMapper.from(undertowProperties::getMaxHttpPostSize).asInt(DataSize::toBytes).when(this::isPositive)
.to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory, maxHttpPostSize));
propertyMapper.from(properties::getConnectionTimeout)
.to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
.to((connectionTimeout) -> customizeNoRequestTimeout(factory, connectionTimeout));
propertyMapper.from(undertowProperties::getNoRequestTimeout).whenNonNull()
.to((connectionTimeout) -> customizeNoRequestTimeout(factory, connectionTimeout));
}
private boolean isPositive(Number value) {
return value.longValue() > 0;
}
private void customizeConnectionTimeout(ConfigurableUndertowWebServerFactory factory, Duration connectionTimeout) {
private void customizeNoRequestTimeout(ConfigurableUndertowWebServerFactory factory, Duration connectionTimeout) {
factory.addBuilderCustomizers((builder) -> builder.setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT,
(int) connectionTimeout.toMillis()));
}

View File

@ -19,10 +19,13 @@ 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.Locale;
import java.util.TimeZone;
import java.util.stream.Collectors;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConfiguration.ConnectionFactory;
@ -159,6 +162,23 @@ public class JettyWebServerFactoryCustomizerTests {
assertThat(requestHeaderSizes).containsOnly(8192);
}
@Test
public void customIdleTimeout() {
bind("server.jetty.idle-timeout=60s");
JettyWebServer server = customizeAndGetServer();
List<Long> timeouts = connectorsIdleTimeouts(server);
assertThat(timeouts).containsOnly(60000L);
}
private List<Long> 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<Integer> getRequestHeaderSizes(JettyWebServer server) {
List<Integer> requestHeaderSizes = new ArrayList<>();
// Start (and directly stop) server to have connectors available

View File

@ -93,21 +93,29 @@ public class NettyWebServerFactoryCustomizerTests {
}
@Test
public void setConnectionTimeoutAsZero() {
setupConnectionTimeout(Duration.ZERO);
public void setServerConnectionTimeoutAsZero() {
setupServerConnectionTimeout(Duration.ZERO);
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
this.customizer.customize(factory);
verifyConnectionTimeout(factory, null);
}
@Test
public void setConnectionTimeoutAsMinusOne() {
setupConnectionTimeout(Duration.ofNanos(-1));
public void setServerConnectionTimeoutAsMinusOne() {
setupServerConnectionTimeout(Duration.ofNanos(-1));
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
this.customizer.customize(factory);
verifyConnectionTimeout(factory, 0);
}
@Test
public void setServerConnectionTimeout() {
setupServerConnectionTimeout(Duration.ofSeconds(1));
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
this.customizer.customize(factory);
verifyConnectionTimeout(factory, 1000);
}
@Test
public void setConnectionTimeout() {
setupConnectionTimeout(Duration.ofSeconds(1));
@ -131,10 +139,16 @@ public 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);
}
}

View File

@ -151,6 +151,14 @@ public class TomcatWebServerFactoryCustomizerTests {
.getMaxSwallowSize()).isEqualTo(DataSize.ofMegabytes(10).toBytes()));
}
@Test
public void customConnectionTimeout() {
bind("server.tomcat.connection-timeout=30s");
customizeAndRunServer((server) -> assertThat(
((AbstractProtocol<?>) server.getTomcat().getConnector().getProtocolHandler()).getConnectionTimeout())
.isEqualTo(30000));
}
@Test
public void customRemoteIpValve() {
bind("server.tomcat.remote-ip-header=x-my-remote-ip-header",

View File

@ -135,13 +135,13 @@ public class UndertowWebServerFactoryCustomizerTests {
@Test
public void customConnectionTimeout() {
bind("server.connection-timeout=100");
bind("server.undertow.no-request-timeout=1m");
Builder builder = Undertow.builder();
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
this.customizer.customize(factory);
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder, "serverOptions")).getMap();
assertThat(map.contains(UndertowOptions.NO_REQUEST_TIMEOUT)).isTrue();
assertThat(map.get(UndertowOptions.NO_REQUEST_TIMEOUT)).isEqualTo(100);
assertThat(map.get(UndertowOptions.NO_REQUEST_TIMEOUT)).isEqualTo(60000);
}
private ConfigurableUndertowWebServerFactory mockFactory(Builder builder) {

View File

@ -207,8 +207,10 @@ Rather, pick only the properties that you need.
server.jetty.accesslog.log-server=false # Enable logging of the request hostname.
server.jetty.accesslog.retention-period=31 # Number of days before rotated log files are deleted.
server.jetty.accesslog.time-zone=GMT # Timezone of the request log.
server.jetty.connection-idle-timeout= # Time that the connection can be idle before it is closed.
server.jetty.max-http-post-size=200000B # Maximum size of the HTTP post or put content.
server.jetty.selectors=-1 # Number of selector threads to use. When the value is -1, the default, the number of selectors is derived from the operating environment.
server.netty.connection-timeout= # Connection timeout of the Netty channel.
server.max-http-header-size=8KB # Maximum size of the HTTP message header.
server.port=8080 # Server HTTP port.
server.server-header= # Value to use for the Server response header (if empty, no header is sent).
@ -259,6 +261,7 @@ Rather, pick only the properties that you need.
server.tomcat.additional-tld-skip-patterns= # Comma-separated list of additional patterns that match jars to ignore for TLD scanning.
server.tomcat.background-processor-delay=10s # Delay between the invocation of backgroundProcess methods. If a duration suffix is not specified, seconds will be used.
server.tomcat.basedir= # Tomcat base directory. If not specified, a temporary directory is used.
server.tomcat.connection-timeout= # Amount of time the connector will wait, after accepting a connection, for the request URI line to be presented.
server.tomcat.internal-proxies=10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\
192\\.168\\.\\d{1,3}\\.\\d{1,3}|\\
169\\.254\\.\\d{1,3}\\.\\d{1,3}|\\
@ -293,6 +296,7 @@ Rather, pick only the properties that you need.
server.undertow.eager-filter-init=true # Whether servlet filters should be initialized on startup.
server.undertow.io-threads= # Number of I/O threads to create for the worker. The default is derived from the number of available processors.
server.undertow.max-http-post-size=-1B # Maximum size of the HTTP post content. When the value is -1, the default, the size is unlimited.
server.undertow.no-request-timeout= # Amount of time a connection can sit idle without processing a request, before it is closed by the server.
server.undertow.worker-threads= # Number of worker threads. The default is 8 times the number of I/O threads.
# FREEMARKER ({spring-boot-autoconfigure-module-code}/freemarker/FreeMarkerProperties.java[FreeMarkerProperties])