Move server-specific properties out of ServerProperties

Issue: 44064
This commit is contained in:
Andy Wilkinson 2025-02-05 08:43:39 +00:00 committed by Phillip Webb
parent ebd4c1a326
commit 90dc10cdee
37 changed files with 2586 additions and 2199 deletions

View File

@ -0,0 +1,359 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.server.jetty;
import java.time.Duration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
/**
* Jetty server properties.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Ivan Sopov
* @author Marcos Barbero
* @author Eddú Meléndez
* @author Quinten De Swaef
* @author Venil Noronha
* @author Aurélien Leboulanger
* @author Brian Clozel
* @author Olivier Lamy
* @author Chentao Qu
* @author Artsiom Yudovin
* @author Andrew McGhie
* @author Rafiullah Hamedy
* @author Dirk Deyne
* @author HaiTao Zhang
* @author Victor Mandujano
* @author Chris Bono
* @author Parviz Rozikov
* @author Florian Storz
* @author Michael Weidmann
* @author Lasse Wulff
* @since 3.5.0
*/
@ConfigurationProperties("server.jetty")
public class JettyServerProperties {
/**
* Maximum size of the form content in any HTTP post request.
*/
private DataSize maxHttpFormPostSize = DataSize.ofBytes(200000);
/**
* Maximum number of form keys.
*/
private int maxFormKeys = 1000;
/**
* Time that the connection can be idle before it is closed.
*/
private Duration connectionIdleTimeout;
/**
* Maximum size of the HTTP response header.
*/
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
/**
* Maximum number of connections that the server accepts and processes at any given
* time.
*/
private int maxConnections = -1;
/**
* Access log configuration.
*/
private final Accesslog accesslog = new Accesslog();
/**
* Thread related configuration.
*/
private final Threads threads = new Threads();
public Accesslog getAccesslog() {
return this.accesslog;
}
public Threads getThreads() {
return this.threads;
}
public DataSize getMaxHttpFormPostSize() {
return this.maxHttpFormPostSize;
}
public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) {
this.maxHttpFormPostSize = maxHttpFormPostSize;
}
public int getMaxFormKeys() {
return this.maxFormKeys;
}
public void setMaxFormKeys(int maxFormKeys) {
this.maxFormKeys = maxFormKeys;
}
public Duration getConnectionIdleTimeout() {
return this.connectionIdleTimeout;
}
public void setConnectionIdleTimeout(Duration connectionIdleTimeout) {
this.connectionIdleTimeout = connectionIdleTimeout;
}
public DataSize getMaxHttpResponseHeaderSize() {
return this.maxHttpResponseHeaderSize;
}
public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) {
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
}
public int getMaxConnections() {
return this.maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
/**
* Jetty access log properties.
*/
public static class Accesslog {
/**
* Enable access log.
*/
private boolean enabled = false;
/**
* Log format.
*/
private Accesslog.FORMAT format = FORMAT.NCSA;
/**
* Custom log format, see org.eclipse.jetty.server.CustomRequestLog. If defined,
* overrides the "format" configuration key.
*/
private String customFormat;
/**
* Log filename. If not specified, logs redirect to "System.err".
*/
private String filename;
/**
* Date format to place in log file name.
*/
private String fileDateFormat;
/**
* Number of days before rotated log files are deleted.
*/
private int retentionPeriod = 31; // no days
/**
* Append to log.
*/
private boolean append;
/**
* Request paths that should not be logged.
*/
private List<String> ignorePaths;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Accesslog.FORMAT getFormat() {
return this.format;
}
public void setFormat(Accesslog.FORMAT format) {
this.format = format;
}
public String getCustomFormat() {
return this.customFormat;
}
public void setCustomFormat(String customFormat) {
this.customFormat = customFormat;
}
public String getFilename() {
return this.filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getFileDateFormat() {
return this.fileDateFormat;
}
public void setFileDateFormat(String fileDateFormat) {
this.fileDateFormat = fileDateFormat;
}
public int getRetentionPeriod() {
return this.retentionPeriod;
}
public void setRetentionPeriod(int retentionPeriod) {
this.retentionPeriod = retentionPeriod;
}
public boolean isAppend() {
return this.append;
}
public void setAppend(boolean append) {
this.append = append;
}
public List<String> getIgnorePaths() {
return this.ignorePaths;
}
public void setIgnorePaths(List<String> ignorePaths) {
this.ignorePaths = ignorePaths;
}
/**
* Log format for Jetty access logs.
*/
public enum FORMAT {
/**
* NCSA format, as defined in CustomRequestLog#NCSA_FORMAT.
*/
NCSA,
/**
* Extended NCSA format, as defined in CustomRequestLog#EXTENDED_NCSA_FORMAT.
*/
EXTENDED_NCSA
}
}
/**
* Jetty thread properties.
*/
public static class Threads {
/**
* Number of acceptor threads to use. When the value is -1, the default, the
* number of acceptors is derived from the operating environment.
*/
private Integer acceptors = -1;
/**
* Number of selector threads to use. When the value is -1, the default, the
* number of selectors is derived from the operating environment.
*/
private Integer selectors = -1;
/**
* Maximum number of threads. Doesn't have an effect if virtual threads are
* enabled.
*/
private Integer max = 200;
/**
* Minimum number of threads. Doesn't have an effect if virtual threads are
* enabled.
*/
private Integer min = 8;
/**
* Maximum capacity of the thread pool's backing queue. A default is computed
* based on the threading configuration.
*/
private Integer maxQueueCapacity;
/**
* Maximum thread idle time.
*/
private Duration idleTimeout = Duration.ofMillis(60000);
public Integer getAcceptors() {
return this.acceptors;
}
public void setAcceptors(Integer acceptors) {
this.acceptors = acceptors;
}
public Integer getSelectors() {
return this.selectors;
}
public void setSelectors(Integer selectors) {
this.selectors = selectors;
}
public void setMin(Integer min) {
this.min = min;
}
public Integer getMin() {
return this.min;
}
public void setMax(Integer max) {
this.max = max;
}
public Integer getMax() {
return this.max;
}
public Integer getMaxQueueCapacity() {
return this.maxQueueCapacity;
}
public void setMaxQueueCapacity(Integer maxQueueCapacity) {
this.maxQueueCapacity = maxQueueCapacity;
}
public void setIdleTimeout(Duration idleTimeout) {
this.idleTimeout = idleTimeout;
}
public Duration getIdleTimeout() {
return this.idleTimeout;
}
}
}

View File

@ -23,11 +23,9 @@ import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.springframework.boot.autoconfigure.web.ServerProperties;
/**
* Creates a {@link ThreadPool} for Jetty, applying
* {@link org.springframework.boot.autoconfigure.web.ServerProperties.Jetty.Threads
* {@link org.springframework.boot.autoconfigure.web.server.jetty.JettyServerProperties.Threads
* ServerProperties.Jetty.Threads Jetty thread properties}.
*
* @author Moritz Halbritter
@ -37,7 +35,7 @@ final class JettyThreadPool {
private JettyThreadPool() {
}
static QueuedThreadPool create(ServerProperties.Jetty.Threads properties) {
static QueuedThreadPool create(JettyServerProperties.Threads properties) {
BlockingQueue<Runnable> queue = determineBlockingQueue(properties.getMaxQueueCapacity());
int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200;
int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8;

View File

@ -19,10 +19,13 @@ package org.springframework.boot.autoconfigure.web.server.jetty;
import org.eclipse.jetty.util.VirtualThreads;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.jetty.ConfigurableJettyWebServerFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
@ -32,18 +35,21 @@ import org.springframework.util.Assert;
* @since 4.0.0
*/
public class JettyVirtualThreadsWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>, Ordered {
implements WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>, Ordered, EnvironmentAware {
private final ServerProperties serverProperties;
private final JettyServerProperties jettyProperties;
public JettyVirtualThreadsWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
private final boolean bind;
public JettyVirtualThreadsWebServerFactoryCustomizer(JettyServerProperties jettyProperties) {
this.jettyProperties = jettyProperties;
this.bind = false;
}
@Override
public void customize(ConfigurableJettyWebServerFactory factory) {
Assert.state(VirtualThreads.areSupported(), "Virtual threads are not supported");
QueuedThreadPool threadPool = JettyThreadPool.create(this.serverProperties.getJetty().getThreads());
QueuedThreadPool threadPool = JettyThreadPool.create(this.jettyProperties.getThreads());
threadPool.setVirtualThreadsExecutor(VirtualThreads.getNamedVirtualThreadsExecutor("jetty-"));
factory.setThreadPool(threadPool);
}
@ -53,4 +59,11 @@ public class JettyVirtualThreadsWebServerFactoryCustomizer
return JettyWebServerFactoryCustomizer.ORDER + 1;
}
@Override
public void setEnvironment(Environment environment) {
if (this.bind) {
Binder.get(environment).bind("server.jetty", Bindable.ofInstance(this.jettyProperties));
}
}
}

View File

@ -38,17 +38,22 @@ import org.springframework.core.env.Environment;
@Configuration(proxyBeanMethods = false)
public class JettyWebServerConfiguration {
private final JettyServerProperties jettyProperties;
public JettyWebServerConfiguration(JettyServerProperties jettyProperties) {
this.jettyProperties = jettyProperties;
}
@Bean
JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
return new JettyWebServerFactoryCustomizer(environment, serverProperties, this.jettyProperties);
}
@Bean
@ConditionalOnThreading(Threading.VIRTUAL)
JettyVirtualThreadsWebServerFactoryCustomizer jettyVirtualThreadsWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new JettyVirtualThreadsWebServerFactoryCustomizer(serverProperties);
JettyVirtualThreadsWebServerFactoryCustomizer jettyVirtualThreadsWebServerFactoryCustomizer() {
return new JettyVirtualThreadsWebServerFactoryCustomizer(this.jettyProperties);
}
}

View File

@ -62,9 +62,13 @@ public class JettyWebServerFactoryCustomizer
private final ServerProperties serverProperties;
public JettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
private final JettyServerProperties jettyProperties;
public JettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties,
JettyServerProperties jettyProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
this.jettyProperties = jettyProperties;
}
@Override
@ -74,34 +78,33 @@ public class JettyWebServerFactoryCustomizer
@Override
public void customize(ConfigurableJettyWebServerFactory factory) {
ServerProperties.Jetty properties = this.serverProperties.getJetty();
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
ServerProperties.Jetty.Threads threadProperties = properties.getThreads();
factory.setThreadPool(JettyThreadPool.create(properties.getThreads()));
JettyServerProperties.Threads threadProperties = this.jettyProperties.getThreads();
factory.setThreadPool(JettyThreadPool.create(this.jettyProperties.getThreads()));
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::getMaxConnections).to(factory::setMaxConnections);
map.from(this.jettyProperties::getMaxConnections).to(factory::setMaxConnections);
map.from(threadProperties::getAcceptors).to(factory::setAcceptors);
map.from(threadProperties::getSelectors).to(factory::setSelectors);
map.from(this.serverProperties::getMaxHttpRequestHeaderSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to(customizeHttpConfigurations(factory, HttpConfiguration::setRequestHeaderSize));
map.from(properties::getMaxHttpResponseHeaderSize)
map.from(this.jettyProperties::getMaxHttpResponseHeaderSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to(customizeHttpConfigurations(factory, HttpConfiguration::setResponseHeaderSize));
map.from(properties::getMaxHttpFormPostSize)
map.from(this.jettyProperties::getMaxHttpFormPostSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormContentSize));
map.from(properties::getMaxFormKeys)
map.from(this.jettyProperties::getMaxFormKeys)
.when(this::isPositive)
.to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormKeys));
map.from(properties::getConnectionIdleTimeout)
map.from(this.jettyProperties::getConnectionIdleTimeout)
.as(Duration::toMillis)
.to(customizeAbstractConnectors(factory, AbstractConnector::setIdleTimeout));
map.from(properties::getAccesslog)
.when(ServerProperties.Jetty.Accesslog::isEnabled)
map.from(this.jettyProperties::getAccesslog)
.when(JettyServerProperties.Accesslog::isEnabled)
.to((accesslog) -> customizeAccessLog(factory, accesslog));
}
@ -177,7 +180,7 @@ public class JettyWebServerFactoryCustomizer
}
private void customizeAccessLog(ConfigurableJettyWebServerFactory factory,
ServerProperties.Jetty.Accesslog properties) {
JettyServerProperties.Accesslog properties) {
factory.addServerCustomizers((server) -> {
RequestLogWriter logWriter = new RequestLogWriter();
String format = getLogFormat(properties);
@ -197,11 +200,11 @@ public class JettyWebServerFactoryCustomizer
});
}
private String getLogFormat(ServerProperties.Jetty.Accesslog properties) {
private String getLogFormat(JettyServerProperties.Accesslog properties) {
if (properties.getCustomFormat() != null) {
return properties.getCustomFormat();
}
if (ServerProperties.Jetty.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) {
if (JettyServerProperties.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) {
return CustomRequestLog.EXTENDED_NCSA_FORMAT;
}
return CustomRequestLog.NCSA_FORMAT;

View File

@ -26,8 +26,10 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.server.jetty.JettyServerProperties;
import org.springframework.boot.autoconfigure.web.server.jetty.JettyWebServerConfiguration;
import org.springframework.boot.autoconfigure.web.server.reactive.ReactiveWebServerConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.jetty.JettyServerCustomizer;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.jetty.JettyReactiveWebServerFactory;
@ -46,6 +48,7 @@ import org.springframework.http.ReactiveHttpInputMessage;
@AutoConfiguration
@ConditionalOnClass({ Server.class, ServletHolder.class, ReactiveHttpInputMessage.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(JettyServerProperties.class)
@Import({ JettyWebServerConfiguration.class, ReactiveWebServerConfiguration.class })
public class JettyReactiveWebServerAutoConfiguration {

View File

@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.reactor.netty.ReactorNettyConfigurations.ReactorResourceFactoryConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.server.reactive.ReactiveWebServerConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.netty.NettyRouteProvider;
@ -45,6 +46,7 @@ import org.springframework.http.client.ReactorResourceFactory;
*/
@AutoConfiguration
@ConditionalOnClass({ ReactiveHttpInputMessage.class, HttpServer.class })
@EnableConfigurationProperties(NettyServerProperties.class)
@Import({ ReactiveWebServerConfiguration.class, ReactorResourceFactoryConfiguration.class })
public class NettyReactiveWebServerAutoConfiguration {
@ -61,8 +63,8 @@ public class NettyReactiveWebServerAutoConfiguration {
@Bean
NettyReactiveWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new NettyReactiveWebServerFactoryCustomizer(environment, serverProperties);
ServerProperties serverProperties, NettyServerProperties nettyProperties) {
return new NettyReactiveWebServerFactoryCustomizer(environment, serverProperties, nettyProperties);
}
}

View File

@ -43,9 +43,13 @@ public class NettyReactiveWebServerFactoryCustomizer
private final ServerProperties serverProperties;
public NettyReactiveWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
private final NettyServerProperties nettyProperties;
public NettyReactiveWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties,
NettyServerProperties nettyProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
this.nettyProperties = nettyProperties;
}
@Override
@ -57,11 +61,10 @@ public class NettyReactiveWebServerFactoryCustomizer
public void customize(NettyReactiveWebServerFactory factory) {
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
ServerProperties.Netty nettyProperties = this.serverProperties.getNetty();
map.from(nettyProperties::getConnectionTimeout)
map.from(this.nettyProperties::getConnectionTimeout)
.to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
map.from(nettyProperties::getIdleTimeout).to((idleTimeout) -> customizeIdleTimeout(factory, idleTimeout));
map.from(nettyProperties::getMaxKeepAliveRequests)
map.from(this.nettyProperties::getIdleTimeout).to((idleTimeout) -> customizeIdleTimeout(factory, idleTimeout));
map.from(this.nettyProperties::getMaxKeepAliveRequests)
.to((maxKeepAliveRequests) -> customizeMaxKeepAliveRequests(factory, maxKeepAliveRequests));
if (this.serverProperties.getHttp2() != null && this.serverProperties.getHttp2().isEnabled()) {
map.from(this.serverProperties.getMaxHttpRequestHeaderSize())
@ -88,16 +91,15 @@ public class NettyReactiveWebServerFactoryCustomizer
propertyMapper.from(this.serverProperties.getMaxHttpRequestHeaderSize())
.to((maxHttpRequestHeader) -> httpRequestDecoderSpec
.maxHeaderSize((int) maxHttpRequestHeader.toBytes()));
ServerProperties.Netty nettyProperties = this.serverProperties.getNetty();
propertyMapper.from(nettyProperties.getMaxInitialLineLength())
propertyMapper.from(this.nettyProperties.getMaxInitialLineLength())
.to((maxInitialLineLength) -> httpRequestDecoderSpec
.maxInitialLineLength((int) maxInitialLineLength.toBytes()));
propertyMapper.from(nettyProperties.getH2cMaxContentLength())
propertyMapper.from(this.nettyProperties.getH2cMaxContentLength())
.to((h2cMaxContentLength) -> httpRequestDecoderSpec
.h2cMaxContentLength((int) h2cMaxContentLength.toBytes()));
propertyMapper.from(nettyProperties.getInitialBufferSize())
propertyMapper.from(this.nettyProperties.getInitialBufferSize())
.to((initialBufferSize) -> httpRequestDecoderSpec.initialBufferSize((int) initialBufferSize.toBytes()));
propertyMapper.from(nettyProperties.isValidateHeaders()).to(httpRequestDecoderSpec::validateHeaders);
propertyMapper.from(this.nettyProperties.isValidateHeaders()).to(httpRequestDecoderSpec::validateHeaders);
return httpRequestDecoderSpec;
}));
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.server.reactive.netty;
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
/**
* Netty server properties.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Ivan Sopov
* @author Marcos Barbero
* @author Eddú Meléndez
* @author Quinten De Swaef
* @author Venil Noronha
* @author Aurélien Leboulanger
* @author Brian Clozel
* @author Olivier Lamy
* @author Chentao Qu
* @author Artsiom Yudovin
* @author Andrew McGhie
* @author Rafiullah Hamedy
* @author Dirk Deyne
* @author HaiTao Zhang
* @author Victor Mandujano
* @author Chris Bono
* @author Parviz Rozikov
* @author Florian Storz
* @author Michael Weidmann
* @author Lasse Wulff
* @since 3.5.0
*/
@ConfigurationProperties("server.netty")
public class NettyServerProperties {
/**
* Connection timeout of the Netty channel.
*/
private Duration connectionTimeout;
/**
* Maximum content length of an H2C upgrade request.
*/
private DataSize h2cMaxContentLength = DataSize.ofBytes(0);
/**
* Initial buffer size for HTTP request decoding.
*/
private DataSize initialBufferSize = DataSize.ofBytes(128);
/**
* Maximum length that can be decoded for an HTTP request's initial line.
*/
private DataSize maxInitialLineLength = DataSize.ofKilobytes(4);
/**
* Maximum number of requests that can be made per connection. By default, a
* connection serves unlimited number of requests.
*/
private Integer maxKeepAliveRequests;
/**
* Whether to validate headers when decoding requests.
*/
private boolean validateHeaders = true;
/**
* Idle timeout of the Netty channel. When not specified, an infinite timeout is used.
*/
private Duration idleTimeout;
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public DataSize getH2cMaxContentLength() {
return this.h2cMaxContentLength;
}
public void setH2cMaxContentLength(DataSize h2cMaxContentLength) {
this.h2cMaxContentLength = h2cMaxContentLength;
}
public DataSize getInitialBufferSize() {
return this.initialBufferSize;
}
public void setInitialBufferSize(DataSize initialBufferSize) {
this.initialBufferSize = initialBufferSize;
}
public DataSize getMaxInitialLineLength() {
return this.maxInitialLineLength;
}
public void setMaxInitialLineLength(DataSize maxInitialLineLength) {
this.maxInitialLineLength = maxInitialLineLength;
}
public Integer getMaxKeepAliveRequests() {
return this.maxKeepAliveRequests;
}
public void setMaxKeepAliveRequests(Integer maxKeepAliveRequests) {
this.maxKeepAliveRequests = maxKeepAliveRequests;
}
public boolean isValidateHeaders() {
return this.validateHeaders;
}
public void setValidateHeaders(boolean validateHeaders) {
this.validateHeaders = validateHeaders;
}
public Duration getIdleTimeout() {
return this.idleTimeout;
}
public void setIdleTimeout(Duration idleTimeout) {
this.idleTimeout = idleTimeout;
}
}

View File

@ -24,9 +24,10 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.server.reactive.ReactiveWebServerConfiguration;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatWebServerConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.tomcat.TomcatReactiveWebServerFactory;
import org.springframework.boot.web.server.tomcat.TomcatConnectorCustomizer;
@ -46,13 +47,14 @@ import org.springframework.http.ReactiveHttpInputMessage;
@AutoConfiguration
@ConditionalOnClass({ ReactiveHttpInputMessage.class, Tomcat.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(TomcatServerProperties.class)
@Import({ TomcatWebServerConfiguration.class, ReactiveWebServerConfiguration.class })
public class TomcatReactiveWebServerAutoConfiguration {
@Bean
TomcatReactiveWebServerFactoryCustomizer tomcatReactiveWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatReactiveWebServerFactoryCustomizer(serverProperties);
TomcatServerProperties tomcatProperties) {
return new TomcatReactiveWebServerFactoryCustomizer(tomcatProperties);
}
@Bean

View File

@ -16,18 +16,13 @@
package org.springframework.boot.autoconfigure.web.server.reactive.tomcat;
import org.apache.catalina.core.AprLifecycleListener;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.UseApr;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.reactive.tomcat.TomcatReactiveWebServerFactory;
import org.springframework.util.Assert;
/**
* {@link WebServerFactoryCustomizer} to apply {@link ServerProperties} to Tomcat reactive
* web servers.
* {@link WebServerFactoryCustomizer} to apply {@link TomcatServerProperties} to Tomcat
* reactive web servers.
*
* @author Andy Wilkinson
* @since 4.0.0
@ -35,35 +30,15 @@ import org.springframework.util.Assert;
public class TomcatReactiveWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<TomcatReactiveWebServerFactory> {
private final ServerProperties serverProperties;
private final TomcatServerProperties tomcatProperties;
public TomcatReactiveWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
public TomcatReactiveWebServerFactoryCustomizer(TomcatServerProperties tomcatProperties) {
this.tomcatProperties = tomcatProperties;
}
@Override
public void customize(TomcatReactiveWebServerFactory factory) {
Tomcat tomcatProperties = this.serverProperties.getTomcat();
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
factory.setUseApr(getUseApr(tomcatProperties.getUseApr()));
}
private boolean getUseApr(UseApr useApr) {
return switch (useApr) {
case ALWAYS -> {
Assert.state(isAprAvailable(), "APR has been configured to 'ALWAYS', but it's not available");
yield true;
}
case WHEN_AVAILABLE -> isAprAvailable();
case NEVER -> false;
};
}
private boolean isAprAvailable() {
// At least one instance of AprLifecycleListener has to be created for
// isAprAvailable() to work
new AprLifecycleListener();
return AprLifecycleListener.isAprAvailable();
factory.setDisableMBeanRegistry(!this.tomcatProperties.getMbeanregistry().isEnabled());
}
}

View File

@ -25,7 +25,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.server.reactive.ReactiveWebServerConfiguration;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowServerProperties;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowWebServerConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.undertow.UndertowReactiveWebServerFactory;
import org.springframework.boot.web.server.undertow.UndertowBuilderCustomizer;
@ -43,6 +45,7 @@ import org.springframework.http.ReactiveHttpInputMessage;
@AutoConfiguration
@ConditionalOnClass({ ReactiveHttpInputMessage.class, Undertow.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(UndertowServerProperties.class)
@Import({ UndertowWebServerConfiguration.class, ReactiveWebServerConfiguration.class })
public class UndertowReactiveWebServerAutoConfiguration {

View File

@ -36,8 +36,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeplo
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.server.jetty.JettyServerProperties;
import org.springframework.boot.autoconfigure.web.server.jetty.JettyWebServerConfiguration;
import org.springframework.boot.autoconfigure.web.server.servlet.ServletWebServerConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.jetty.JettyServerCustomizer;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
@ -58,6 +60,7 @@ import org.springframework.core.annotation.Order;
@AutoConfiguration
@ConditionalOnClass({ ServletRequest.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(JettyServerProperties.class)
@Import({ JettyWebServerConfiguration.class, ServletWebServerConfiguration.class })
public class JettyServletWebServerAutoConfiguration {

View File

@ -32,7 +32,9 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.server.servlet.ForwardedHeaderFilterCustomizer;
import org.springframework.boot.autoconfigure.web.server.servlet.ServletWebServerConfiguration;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatWebServerConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.boot.web.server.servlet.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.tomcat.TomcatConnectorCustomizer;
@ -51,9 +53,16 @@ import org.springframework.context.annotation.Import;
@AutoConfiguration
@ConditionalOnClass({ ServletRequest.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(TomcatServerProperties.class)
@Import({ ServletWebServerConfiguration.class, TomcatWebServerConfiguration.class })
public class TomcatServletWebServerAutoConfiguration {
private final TomcatServerProperties tomcatProperties;
public TomcatServletWebServerAutoConfiguration(TomcatServerProperties tomcatProperties) {
this.tomcatProperties = tomcatProperties;
}
@Bean
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
TomcatServletWebServerFactory tomcatServletWebServerFactory(
@ -68,14 +77,15 @@ public class TomcatServletWebServerAutoConfiguration {
}
@Bean
TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
TomcatServerProperties tomcatProperties) {
return new TomcatServletWebServerFactoryCustomizer(tomcatProperties);
}
@Bean
@ConditionalOnProperty(name = "server.forward-headers-strategy", havingValue = "framework")
ForwardedHeaderFilterCustomizer tomcatForwardedHeaderFilterCustomizer(ServerProperties serverProperties) {
return (filter) -> filter.setRelativeRedirects(serverProperties.getTomcat().isUseRelativeRedirects());
return (filter) -> filter.setRelativeRedirects(this.tomcatProperties.isUseRelativeRedirects());
}
}

View File

@ -16,8 +16,7 @@
package org.springframework.boot.autoconfigure.web.server.servlet.tomcat;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.UseApr;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.servlet.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.tomcat.ConfigurableTomcatWebServerFactory;
@ -25,8 +24,8 @@ import org.springframework.core.Ordered;
import org.springframework.util.ObjectUtils;
/**
* {@link WebServerFactoryCustomizer} to apply {@link ServerProperties} to Tomcat web
* servers.
* {@link WebServerFactoryCustomizer} to apply {@link TomcatServerProperties} to Tomcat
* web servers.
*
* @author Brian Clozel
* @author Phillip Webb
@ -34,10 +33,10 @@ import org.springframework.util.ObjectUtils;
class TomcatServletWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {
private final ServerProperties serverProperties;
private final TomcatServerProperties tomcatProperties;
TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
TomcatServletWebServerFactoryCustomizer(TomcatServerProperties tomcatProperties) {
this.tomcatProperties = tomcatProperties;
}
@Override
@ -47,15 +46,14 @@ class TomcatServletWebServerFactoryCustomizer
@Override
public void customize(TomcatServletWebServerFactory factory) {
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
if (!ObjectUtils.isEmpty(this.tomcatProperties.getAdditionalTldSkipPatterns())) {
factory.getTldSkipPatterns().addAll(this.tomcatProperties.getAdditionalTldSkipPatterns());
}
if (tomcatProperties.getRedirectContextRoot() != null) {
customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
if (this.tomcatProperties.getRedirectContextRoot() != null) {
customizeRedirectContextRoot(factory, this.tomcatProperties.getRedirectContextRoot());
}
customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
customizeUseRelativeRedirects(factory, this.tomcatProperties.isUseRelativeRedirects());
factory.setDisableMBeanRegistry(!this.tomcatProperties.getMbeanregistry().isEnabled());
}
private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {

View File

@ -29,9 +29,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.server.servlet.ServletWebServerConfiguration;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowServerProperties;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowWebServerConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.boot.web.server.servlet.undertow.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.web.server.servlet.undertow.UndertowServletWebServerFactory;
@ -50,6 +51,7 @@ import org.springframework.context.annotation.Import;
@AutoConfiguration
@ConditionalOnClass({ ServletRequest.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(UndertowServerProperties.class)
@Import({ UndertowWebServerConfiguration.class, ServletWebServerConfiguration.class })
public class UndertowServletWebServerAutoConfiguration {
@ -66,8 +68,8 @@ public class UndertowServletWebServerAutoConfiguration {
@Bean
UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new UndertowServletWebServerFactoryCustomizer(serverProperties);
UndertowServerProperties undertowProperties) {
return new UndertowServletWebServerFactoryCustomizer(undertowProperties);
}
@Configuration(proxyBeanMethods = false)

View File

@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.web.server.servlet.undertow;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowServerProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.servlet.undertow.UndertowServletWebServerFactory;
@ -28,16 +29,16 @@ import org.springframework.boot.web.server.servlet.undertow.UndertowServletWebSe
*/
class UndertowServletWebServerFactoryCustomizer implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
private final ServerProperties serverProperties;
private final UndertowServerProperties undertowProperties;
UndertowServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
UndertowServletWebServerFactoryCustomizer(UndertowServerProperties undertowProperties) {
this.undertowProperties = undertowProperties;
}
@Override
public void customize(UndertowServletWebServerFactory factory) {
factory.setEagerFilterInit(this.serverProperties.getUndertow().isEagerFilterInit());
factory.setPreservePathOnForward(this.serverProperties.getUndertow().isPreservePathOnForward());
factory.setEagerFilterInit(this.undertowProperties.isEagerFilterInit());
factory.setPreservePathOnForward(this.undertowProperties.isPreservePathOnForward());
}
}

View File

@ -0,0 +1,840 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.server.tomcat;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.util.unit.DataSize;
/**
* Tomcat server properties.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Ivan Sopov
* @author Marcos Barbero
* @author Eddú Meléndez
* @author Quinten De Swaef
* @author Venil Noronha
* @author Aurélien Leboulanger
* @author Brian Clozel
* @author Olivier Lamy
* @author Chentao Qu
* @author Artsiom Yudovin
* @author Andrew McGhie
* @author Rafiullah Hamedy
* @author Dirk Deyne
* @author HaiTao Zhang
* @author Victor Mandujano
* @author Chris Bono
* @author Parviz Rozikov
* @author Florian Storz
* @author Michael Weidmann
* @author Lasse Wulff
* @since 3.5.0
*/
@ConfigurationProperties("server.tomcat")
public class TomcatServerProperties {
/**
* Tomcat base directory. If not specified, a temporary directory is used.
*/
private File basedir;
/**
* Delay between the invocation of backgroundProcess methods. If a duration suffix is
* not specified, seconds will be used.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration backgroundProcessorDelay = Duration.ofSeconds(10);
/**
* Maximum size of the form content in any HTTP post request.
*/
private DataSize maxHttpFormPostSize = DataSize.ofMegabytes(2);
/**
* Maximum amount of request body to swallow.
*/
private DataSize maxSwallowSize = DataSize.ofMegabytes(2);
/**
* Whether requests to the context root should be redirected by appending a / to the
* path. When using SSL terminated at a proxy, this property should be set to false.
*/
private Boolean redirectContextRoot = true;
/**
* Whether HTTP 1.1 and later location headers generated by a call to sendRedirect
* will use relative or absolute redirects.
*/
private boolean useRelativeRedirects;
/**
* Character encoding to use to decode the URI.
*/
private Charset uriEncoding = StandardCharsets.UTF_8;
/**
* Maximum number of connections that the server accepts and processes at any given
* time. Once the limit has been reached, the operating system may still accept
* connections based on the "acceptCount" property.
*/
private int maxConnections = 8192;
/**
* Maximum queue length for incoming connection requests when all possible request
* processing threads are in use.
*/
private int acceptCount = 100;
/**
* Maximum number of idle processors that will be retained in the cache and reused
* with a subsequent request. When set to -1 the cache will be unlimited with a
* theoretical maximum size equal to the maximum number of connections.
*/
private int processorCache = 200;
/**
* Time to wait for another HTTP request before the connection is closed. When not set
* the connectionTimeout is used. When set to -1 there will be no timeout.
*/
private Duration keepAliveTimeout;
/**
* Maximum number of HTTP requests that can be pipelined before the connection is
* closed. When set to 0 or 1, keep-alive and pipelining are disabled. When set to -1,
* an unlimited number of pipelined or keep-alive requests are allowed.
*/
private int maxKeepAliveRequests = 100;
/**
* List of additional patterns that match jars to ignore for TLD scanning. The special
* '?' and '*' characters can be used in the pattern to match one and only one
* character and zero or more characters respectively.
*/
private List<String> additionalTldSkipPatterns = new ArrayList<>();
/**
* List of additional unencoded characters that should be allowed in URI paths. Only
* "< > [ \ ] ^ ` { | }" are allowed.
*/
private List<Character> relaxedPathChars = new ArrayList<>();
/**
* List of additional unencoded characters that should be allowed in URI query
* strings. Only "< > [ \ ] ^ ` { | }" are allowed.
*/
private List<Character> 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;
/**
* Maximum size of the HTTP response header.
*/
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
/**
* Maximum number of parameters (GET plus POST) that will be automatically parsed by
* the container. A value of less than 0 means no limit.
*/
private int maxParameterCount = 10000;
/**
* Whether to use APR.
*/
private UseApr useApr = UseApr.NEVER;
/**
* Access log configuration.
*/
private final Accesslog accesslog = new Accesslog();
/**
* Thread related configuration.
*/
private final Threads threads = new Threads();
/**
* Static resource configuration.
*/
private final Resource resource = new Resource();
/**
* Modeler MBean Registry configuration.
*/
private final Mbeanregistry mbeanregistry = new Mbeanregistry();
/**
* Remote Ip Valve configuration.
*/
private final Remoteip remoteip = new Remoteip();
public Duration getBackgroundProcessorDelay() {
return this.backgroundProcessorDelay;
}
public void setBackgroundProcessorDelay(Duration backgroundProcessorDelay) {
this.backgroundProcessorDelay = backgroundProcessorDelay;
}
public File getBasedir() {
return this.basedir;
}
public void setBasedir(File basedir) {
this.basedir = basedir;
}
public Boolean getRedirectContextRoot() {
return this.redirectContextRoot;
}
public void setRedirectContextRoot(Boolean redirectContextRoot) {
this.redirectContextRoot = redirectContextRoot;
}
public boolean isUseRelativeRedirects() {
return this.useRelativeRedirects;
}
public void setUseRelativeRedirects(boolean useRelativeRedirects) {
this.useRelativeRedirects = useRelativeRedirects;
}
public Charset getUriEncoding() {
return this.uriEncoding;
}
public void setUriEncoding(Charset uriEncoding) {
this.uriEncoding = uriEncoding;
}
public int getMaxConnections() {
return this.maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public DataSize getMaxSwallowSize() {
return this.maxSwallowSize;
}
public void setMaxSwallowSize(DataSize maxSwallowSize) {
this.maxSwallowSize = maxSwallowSize;
}
public int getAcceptCount() {
return this.acceptCount;
}
public void setAcceptCount(int acceptCount) {
this.acceptCount = acceptCount;
}
public int getProcessorCache() {
return this.processorCache;
}
public void setProcessorCache(int processorCache) {
this.processorCache = processorCache;
}
public Duration getKeepAliveTimeout() {
return this.keepAliveTimeout;
}
public void setKeepAliveTimeout(Duration keepAliveTimeout) {
this.keepAliveTimeout = keepAliveTimeout;
}
public int getMaxKeepAliveRequests() {
return this.maxKeepAliveRequests;
}
public void setMaxKeepAliveRequests(int maxKeepAliveRequests) {
this.maxKeepAliveRequests = maxKeepAliveRequests;
}
public List<String> getAdditionalTldSkipPatterns() {
return this.additionalTldSkipPatterns;
}
public void setAdditionalTldSkipPatterns(List<String> additionalTldSkipPatterns) {
this.additionalTldSkipPatterns = additionalTldSkipPatterns;
}
public List<Character> getRelaxedPathChars() {
return this.relaxedPathChars;
}
public void setRelaxedPathChars(List<Character> relaxedPathChars) {
this.relaxedPathChars = relaxedPathChars;
}
public List<Character> getRelaxedQueryChars() {
return this.relaxedQueryChars;
}
public void setRelaxedQueryChars(List<Character> relaxedQueryChars) {
this.relaxedQueryChars = relaxedQueryChars;
}
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public DataSize getMaxHttpResponseHeaderSize() {
return this.maxHttpResponseHeaderSize;
}
public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) {
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
}
public DataSize getMaxHttpFormPostSize() {
return this.maxHttpFormPostSize;
}
public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) {
this.maxHttpFormPostSize = maxHttpFormPostSize;
}
public int getMaxParameterCount() {
return this.maxParameterCount;
}
public void setMaxParameterCount(int maxParameterCount) {
this.maxParameterCount = maxParameterCount;
}
public UseApr getUseApr() {
return this.useApr;
}
public void setUseApr(UseApr useApr) {
this.useApr = useApr;
}
public Accesslog getAccesslog() {
return this.accesslog;
}
public Threads getThreads() {
return this.threads;
}
public Resource getResource() {
return this.resource;
}
public Mbeanregistry getMbeanregistry() {
return this.mbeanregistry;
}
public Remoteip getRemoteip() {
return this.remoteip;
}
/**
* Tomcat access log properties.
*/
public static class Accesslog {
/**
* Enable access log.
*/
private boolean enabled = false;
/**
* Whether logging of the request will only be enabled if
* "ServletRequest.getAttribute(conditionIf)" does not yield null.
*/
private String conditionIf;
/**
* Whether logging of the request will only be enabled if
* "ServletRequest.getAttribute(conditionUnless)" yield null.
*/
private String conditionUnless;
/**
* Format pattern for access logs.
*/
private String pattern = "common";
/**
* Directory in which log files are created. Can be absolute or relative to the
* Tomcat base dir.
*/
private String directory = "logs";
/**
* Log file name prefix.
*/
protected String prefix = "access_log";
/**
* Log file name suffix.
*/
private String suffix = ".log";
/**
* Character set used by the log file. Default to the system default character
* set.
*/
private String encoding;
/**
* Locale used to format timestamps in log entries and in log file name suffix.
* Default to the default locale of the Java process.
*/
private String locale;
/**
* Whether to check for log file existence so it can be recreated if an external
* process has renamed it.
*/
private boolean checkExists = false;
/**
* Whether to enable access log rotation.
*/
private boolean rotate = true;
/**
* Whether to defer inclusion of the date stamp in the file name until rotate
* time.
*/
private boolean renameOnRotate = false;
/**
* Number of days to retain the access log files before they are removed.
*/
private int maxDays = -1;
/**
* Date format to place in the log file name.
*/
private String fileDateFormat = ".yyyy-MM-dd";
/**
* Whether to use IPv6 canonical representation format as defined by RFC 5952.
*/
private boolean ipv6Canonical = false;
/**
* Set request attributes for the IP address, Hostname, protocol, and port used
* for the request.
*/
private boolean requestAttributesEnabled = false;
/**
* Whether to buffer output such that it is flushed only periodically.
*/
private boolean buffered = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getConditionIf() {
return this.conditionIf;
}
public void setConditionIf(String conditionIf) {
this.conditionIf = conditionIf;
}
public String getConditionUnless() {
return this.conditionUnless;
}
public void setConditionUnless(String conditionUnless) {
this.conditionUnless = conditionUnless;
}
public String getPattern() {
return this.pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public String getDirectory() {
return this.directory;
}
public void setDirectory(String directory) {
this.directory = directory;
}
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return this.suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getEncoding() {
return this.encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public String getLocale() {
return this.locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public boolean isCheckExists() {
return this.checkExists;
}
public void setCheckExists(boolean checkExists) {
this.checkExists = checkExists;
}
public boolean isRotate() {
return this.rotate;
}
public void setRotate(boolean rotate) {
this.rotate = rotate;
}
public boolean isRenameOnRotate() {
return this.renameOnRotate;
}
public void setRenameOnRotate(boolean renameOnRotate) {
this.renameOnRotate = renameOnRotate;
}
public int getMaxDays() {
return this.maxDays;
}
public void setMaxDays(int maxDays) {
this.maxDays = maxDays;
}
public String getFileDateFormat() {
return this.fileDateFormat;
}
public void setFileDateFormat(String fileDateFormat) {
this.fileDateFormat = fileDateFormat;
}
public boolean isIpv6Canonical() {
return this.ipv6Canonical;
}
public void setIpv6Canonical(boolean ipv6Canonical) {
this.ipv6Canonical = ipv6Canonical;
}
public boolean isRequestAttributesEnabled() {
return this.requestAttributesEnabled;
}
public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
this.requestAttributesEnabled = requestAttributesEnabled;
}
public boolean isBuffered() {
return this.buffered;
}
public void setBuffered(boolean buffered) {
this.buffered = buffered;
}
}
/**
* Tomcat thread properties.
*/
public static class Threads {
/**
* Maximum amount of worker threads. Doesn't have an effect if virtual threads are
* enabled.
*/
private int max = 200;
/**
* Minimum amount of worker threads. Doesn't have an effect if virtual threads are
* enabled.
*/
private int minSpare = 10;
/**
* Maximum capacity of the thread pool's backing queue. This setting only has an
* effect if the value is greater than 0.
*/
private int maxQueueCapacity = 2147483647;
public int getMax() {
return this.max;
}
public void setMax(int max) {
this.max = max;
}
public int getMinSpare() {
return this.minSpare;
}
public void setMinSpare(int minSpare) {
this.minSpare = minSpare;
}
public int getMaxQueueCapacity() {
return this.maxQueueCapacity;
}
public void setMaxQueueCapacity(int maxQueueCapacity) {
this.maxQueueCapacity = maxQueueCapacity;
}
}
/**
* Tomcat static resource properties.
*/
public static class Resource {
/**
* Whether static resource caching is permitted for this web application.
*/
private boolean allowCaching = true;
/**
* Time-to-live of the static resource cache.
*/
private Duration cacheTtl;
public boolean isAllowCaching() {
return this.allowCaching;
}
public void setAllowCaching(boolean allowCaching) {
this.allowCaching = allowCaching;
}
public Duration getCacheTtl() {
return this.cacheTtl;
}
public void setCacheTtl(Duration cacheTtl) {
this.cacheTtl = cacheTtl;
}
}
public static class Mbeanregistry {
/**
* Whether Tomcat's MBean Registry should be enabled.
*/
private boolean enabled;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public static class Remoteip {
/**
* Regular expression that matches proxies that are to be trusted.
*/
private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8
+ "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
+ "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16
+ "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8
+ "100\\.6[4-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 100.64.0.0/10
+ "100\\.[7-9]{1}\\d{1}\\.\\d{1,3}\\.\\d{1,3}|" // 100.64.0.0/10
+ "100\\.1[0-1]{1}\\d{1}\\.\\d{1,3}\\.\\d{1,3}|" // 100.64.0.0/10
+ "100\\.12[0-7]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 100.64.0.0/10
+ "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
+ "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
+ "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
+ "0:0:0:0:0:0:0:1|" // 0:0:0:0:0:0:0:1
+ "::1|" // ::1
+ "fe[89ab]\\p{XDigit}:.*|" //
+ "f[cd]\\p{XDigit}{2}+:.*";
/**
* Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
*/
private String protocolHeader;
/**
* Value of the protocol header indicating whether the incoming request uses SSL.
*/
private String protocolHeaderHttpsValue = "https";
/**
* Name of the HTTP header from which the remote host is extracted.
*/
private String hostHeader = "X-Forwarded-Host";
/**
* Name of the HTTP header used to override the original port value.
*/
private String portHeader = "X-Forwarded-Port";
/**
* Name of the HTTP header from which the remote IP is extracted. For instance,
* 'X-FORWARDED-FOR'.
*/
private String remoteIpHeader;
/**
* Regular expression defining proxies that are trusted when they appear in the
* "remote-ip-header" header.
*/
private String trustedProxies;
public String getInternalProxies() {
return this.internalProxies;
}
public void setInternalProxies(String internalProxies) {
this.internalProxies = internalProxies;
}
public String getProtocolHeader() {
return this.protocolHeader;
}
public void setProtocolHeader(String protocolHeader) {
this.protocolHeader = protocolHeader;
}
public String getProtocolHeaderHttpsValue() {
return this.protocolHeaderHttpsValue;
}
public String getHostHeader() {
return this.hostHeader;
}
public void setHostHeader(String hostHeader) {
this.hostHeader = hostHeader;
}
public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) {
this.protocolHeaderHttpsValue = protocolHeaderHttpsValue;
}
public String getPortHeader() {
return this.portHeader;
}
public void setPortHeader(String portHeader) {
this.portHeader = portHeader;
}
public String getRemoteIpHeader() {
return this.remoteIpHeader;
}
public void setRemoteIpHeader(String remoteIpHeader) {
this.remoteIpHeader = remoteIpHeader;
}
public String getTrustedProxies() {
return this.trustedProxies;
}
public void setTrustedProxies(String trustedProxies) {
this.trustedProxies = trustedProxies;
}
}
/**
* When to use APR.
*/
public enum UseApr {
/**
* Always use APR and fail if it's not available.
*/
ALWAYS,
/**
* Use APR if it is available.
*/
WHEN_AVAILABLE,
/**
* Never use APR.
*/
NEVER
}
}

View File

@ -43,8 +43,8 @@ public class TomcatWebServerConfiguration {
@Bean
TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
ServerProperties serverProperties, TomcatServerProperties tomcatProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties, tomcatProperties);
}
@Bean

View File

@ -35,9 +35,9 @@ import org.apache.coyote.http2.Http2Protocol;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeAttribute;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Remoteip;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.UseApr;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties.Accesslog;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties.Remoteip;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties.UseApr;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
@ -75,9 +75,13 @@ public class TomcatWebServerFactoryCustomizer
private final ServerProperties serverProperties;
public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
private final TomcatServerProperties tomcatProperties;
public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties,
TomcatServerProperties tomcatProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
this.tomcatProperties = tomcatProperties;
}
@Override
@ -88,15 +92,14 @@ public class TomcatWebServerFactoryCustomizer
@Override
@SuppressWarnings("removal")
public void customize(ConfigurableTomcatWebServerFactory factory) {
ServerProperties.Tomcat properties = this.serverProperties.getTomcat();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::getBasedir).to(factory::setBaseDirectory);
map.from(properties::getBackgroundProcessorDelay)
map.from(this.tomcatProperties::getBasedir).to(factory::setBaseDirectory);
map.from(this.tomcatProperties::getBackgroundProcessorDelay)
.as(Duration::getSeconds)
.as(Long::intValue)
.to(factory::setBackgroundProcessorDelay);
customizeRemoteIpValve(factory);
ServerProperties.Tomcat.Threads threadProperties = properties.getThreads();
TomcatServerProperties.Threads threadProperties = this.tomcatProperties.getThreads();
map.from(threadProperties::getMax)
.when(this::isPositive)
.to((maxThreads) -> customizeMaxThreads(factory, maxThreads));
@ -110,48 +113,48 @@ public class TomcatWebServerFactoryCustomizer
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to((maxHttpRequestHeaderSize) -> customizeMaxHttpRequestHeaderSize(factory, maxHttpRequestHeaderSize));
map.from(properties::getMaxHttpResponseHeaderSize)
map.from(this.tomcatProperties::getMaxHttpResponseHeaderSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to((maxHttpResponseHeaderSize) -> customizeMaxHttpResponseHeaderSize(factory, maxHttpResponseHeaderSize));
map.from(properties::getMaxSwallowSize)
map.from(this.tomcatProperties::getMaxSwallowSize)
.asInt(DataSize::toBytes)
.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
map.from(properties::getMaxHttpFormPostSize)
map.from(this.tomcatProperties::getMaxHttpFormPostSize)
.asInt(DataSize::toBytes)
.when((maxHttpFormPostSize) -> maxHttpFormPostSize != 0)
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
map.from(properties::getMaxParameterCount)
map.from(this.tomcatProperties::getMaxParameterCount)
.to((maxParameterCount) -> customizeMaxParameterCount(factory, maxParameterCount));
map.from(properties::getAccesslog)
.when(ServerProperties.Tomcat.Accesslog::isEnabled)
map.from(this.tomcatProperties::getAccesslog)
.when(TomcatServerProperties.Accesslog::isEnabled)
.to((enabled) -> customizeAccessLog(factory));
map.from(properties::getUriEncoding).to(factory::setUriEncoding);
map.from(properties::getConnectionTimeout)
map.from(this.tomcatProperties::getUriEncoding).to(factory::setUriEncoding);
map.from(this.tomcatProperties::getConnectionTimeout)
.to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
map.from(properties::getMaxConnections)
map.from(this.tomcatProperties::getMaxConnections)
.when(this::isPositive)
.to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
map.from(properties::getAcceptCount)
map.from(this.tomcatProperties::getAcceptCount)
.when(this::isPositive)
.to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
map.from(properties::getProcessorCache)
map.from(this.tomcatProperties::getProcessorCache)
.to((processorCache) -> customizeProcessorCache(factory, processorCache));
map.from(properties::getKeepAliveTimeout)
map.from(this.tomcatProperties::getKeepAliveTimeout)
.to((keepAliveTimeout) -> customizeKeepAliveTimeout(factory, keepAliveTimeout));
map.from(properties::getMaxKeepAliveRequests)
map.from(this.tomcatProperties::getMaxKeepAliveRequests)
.to((maxKeepAliveRequests) -> customizeMaxKeepAliveRequests(factory, maxKeepAliveRequests));
map.from(properties::getRelaxedPathChars)
map.from(this.tomcatProperties::getRelaxedPathChars)
.as(this::joinCharacters)
.whenHasText()
.to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars));
map.from(properties::getRelaxedQueryChars)
map.from(this.tomcatProperties::getRelaxedQueryChars)
.as(this::joinCharacters)
.whenHasText()
.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
customizeStaticResources(factory);
customizeErrorReportValve(this.serverProperties.getError(), factory);
factory.setUseApr(getUseApr(this.serverProperties.getTomcat().getUseApr()));
factory.setUseApr(getUseApr(this.tomcatProperties.getUseApr()));
}
private boolean getUseApr(UseApr useApr) {
@ -245,7 +248,7 @@ public class TomcatWebServerFactoryCustomizer
}
private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) {
Remoteip remoteIpProperties = this.serverProperties.getTomcat().getRemoteip();
Remoteip remoteIpProperties = this.tomcatProperties.getRemoteip();
String protocolHeader = remoteIpProperties.getProtocolHeader();
String remoteIpHeader = remoteIpProperties.getRemoteIpHeader();
// For back compatibility the valve is also enabled if protocol-header is set
@ -320,10 +323,9 @@ public class TomcatWebServerFactoryCustomizer
}
private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) {
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
AccessLogValve valve = new AccessLogValve();
PropertyMapper map = PropertyMapper.get();
Accesslog accessLogConfig = tomcatProperties.getAccesslog();
Accesslog accessLogConfig = this.tomcatProperties.getAccesslog();
map.from(accessLogConfig.getConditionIf()).to(valve::setConditionIf);
map.from(accessLogConfig.getConditionUnless()).to(valve::setConditionUnless);
map.from(accessLogConfig.getPattern()).to(valve::setPattern);
@ -344,7 +346,7 @@ public class TomcatWebServerFactoryCustomizer
}
private void customizeStaticResources(ConfigurableTomcatWebServerFactory factory) {
ServerProperties.Tomcat.Resource resource = this.serverProperties.getTomcat().getResource();
TomcatServerProperties.Resource resource = this.tomcatProperties.getResource();
factory.addContextCustomizers((context) -> context.addLifecycleListener((event) -> {
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
context.getResources().setCachingAllowed(resource.isAllowCaching());

View File

@ -0,0 +1,425 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.server.undertow;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import io.undertow.UndertowOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.util.unit.DataSize;
/**
* Undertow server properties.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Ivan Sopov
* @author Marcos Barbero
* @author Eddú Meléndez
* @author Quinten De Swaef
* @author Venil Noronha
* @author Aurélien Leboulanger
* @author Brian Clozel
* @author Olivier Lamy
* @author Chentao Qu
* @author Artsiom Yudovin
* @author Andrew McGhie
* @author Rafiullah Hamedy
* @author Dirk Deyne
* @author HaiTao Zhang
* @author Victor Mandujano
* @author Chris Bono
* @author Parviz Rozikov
* @author Florian Storz
* @author Michael Weidmann
* @author Lasse Wulff
* @since 3.5.0
*/
@ConfigurationProperties("server.undertow")
public class UndertowServerProperties {
/**
* Maximum size of the HTTP post content. When the value is -1, the default, the size
* is unlimited.
*/
private DataSize maxHttpPostSize = DataSize.ofBytes(-1);
/**
* Size of each buffer. The default is derived from the maximum amount of memory that
* is available to the JVM.
*/
private DataSize bufferSize;
/**
* Whether to allocate buffers outside the Java heap. The default is derived from the
* maximum amount of memory that is available to the JVM.
*/
private Boolean directBuffers;
/**
* Whether servlet filters should be initialized on startup.
*/
private boolean eagerFilterInit = true;
/**
* Maximum number of query or path parameters that are allowed. This limit exists to
* prevent hash collision based DOS attacks.
*/
private int maxParameters = UndertowOptions.DEFAULT_MAX_PARAMETERS;
/**
* Maximum number of headers that are allowed. This limit exists to prevent hash
* collision based DOS attacks.
*/
private int maxHeaders = UndertowOptions.DEFAULT_MAX_HEADERS;
/**
* Maximum number of cookies that are allowed. This limit exists to prevent hash
* collision based DOS attacks.
*/
private int maxCookies = 200;
/**
* Whether the server should decode percent encoded slash characters. Enabling encoded
* slashes can have security implications due to different servers interpreting the
* slash differently. Only enable this if you have a legacy application that requires
* it. Has no effect when server.undertow.decode-slash is set.
*/
private boolean allowEncodedSlash = false;
/**
* Whether encoded slash characters (%2F) should be decoded. Decoding can cause
* security problems if a front-end proxy does not perform the same decoding. Only
* enable this if you have a legacy application that requires it. When set,
* server.undertow.allow-encoded-slash has no effect.
*/
private Boolean decodeSlash;
/**
* Whether the URL should be decoded. When disabled, percent-encoded characters in the
* URL will be left as-is.
*/
private boolean decodeUrl = true;
/**
* Charset used to decode URLs.
*/
private Charset urlCharset = StandardCharsets.UTF_8;
/**
* Whether the 'Connection: keep-alive' header should be added to all responses, even
* if not required by the HTTP specification.
*/
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;
/**
* Whether to preserve the path of a request when it is forwarded.
*/
private boolean preservePathOnForward = false;
private final Accesslog accesslog = new Accesslog();
/**
* Thread related configuration.
*/
private final Threads threads = new Threads();
private final Options options = new Options();
public DataSize getMaxHttpPostSize() {
return this.maxHttpPostSize;
}
public void setMaxHttpPostSize(DataSize maxHttpPostSize) {
this.maxHttpPostSize = maxHttpPostSize;
}
public DataSize getBufferSize() {
return this.bufferSize;
}
public void setBufferSize(DataSize bufferSize) {
this.bufferSize = bufferSize;
}
public Boolean getDirectBuffers() {
return this.directBuffers;
}
public void setDirectBuffers(Boolean directBuffers) {
this.directBuffers = directBuffers;
}
public boolean isEagerFilterInit() {
return this.eagerFilterInit;
}
public void setEagerFilterInit(boolean eagerFilterInit) {
this.eagerFilterInit = eagerFilterInit;
}
public int getMaxParameters() {
return this.maxParameters;
}
public void setMaxParameters(Integer maxParameters) {
this.maxParameters = maxParameters;
}
public int getMaxHeaders() {
return this.maxHeaders;
}
public void setMaxHeaders(int maxHeaders) {
this.maxHeaders = maxHeaders;
}
public Integer getMaxCookies() {
return this.maxCookies;
}
public void setMaxCookies(Integer maxCookies) {
this.maxCookies = maxCookies;
}
@DeprecatedConfigurationProperty(replacement = "server.undertow.decode-slash", since = "3.0.3")
@Deprecated(forRemoval = true, since = "3.0.3")
public boolean isAllowEncodedSlash() {
return this.allowEncodedSlash;
}
@Deprecated(forRemoval = true, since = "3.0.3")
public void setAllowEncodedSlash(boolean allowEncodedSlash) {
this.allowEncodedSlash = allowEncodedSlash;
}
public Boolean getDecodeSlash() {
return this.decodeSlash;
}
public void setDecodeSlash(Boolean decodeSlash) {
this.decodeSlash = decodeSlash;
}
public boolean isDecodeUrl() {
return this.decodeUrl;
}
public void setDecodeUrl(Boolean decodeUrl) {
this.decodeUrl = decodeUrl;
}
public Charset getUrlCharset() {
return this.urlCharset;
}
public void setUrlCharset(Charset urlCharset) {
this.urlCharset = urlCharset;
}
public boolean isAlwaysSetKeepAlive() {
return this.alwaysSetKeepAlive;
}
public void setAlwaysSetKeepAlive(boolean alwaysSetKeepAlive) {
this.alwaysSetKeepAlive = alwaysSetKeepAlive;
}
public Duration getNoRequestTimeout() {
return this.noRequestTimeout;
}
public void setNoRequestTimeout(Duration noRequestTimeout) {
this.noRequestTimeout = noRequestTimeout;
}
public boolean isPreservePathOnForward() {
return this.preservePathOnForward;
}
public void setPreservePathOnForward(boolean preservePathOnForward) {
this.preservePathOnForward = preservePathOnForward;
}
public UndertowServerProperties.Accesslog getAccesslog() {
return this.accesslog;
}
public UndertowServerProperties.Threads getThreads() {
return this.threads;
}
public UndertowServerProperties.Options getOptions() {
return this.options;
}
/**
* Undertow access log properties.
*/
public static class Accesslog {
/**
* Whether to enable the access log.
*/
private boolean enabled = false;
/**
* Format pattern for access logs.
*/
private String pattern = "common";
/**
* Log file name prefix.
*/
protected String prefix = "access_log.";
/**
* Log file name suffix.
*/
private String suffix = "log";
/**
* Undertow access log directory.
*/
private File dir = new File("logs");
/**
* Whether to enable access log rotation.
*/
private boolean rotate = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getPattern() {
return this.pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return this.suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public File getDir() {
return this.dir;
}
public void setDir(File dir) {
this.dir = dir;
}
public boolean isRotate() {
return this.rotate;
}
public void setRotate(boolean rotate) {
this.rotate = rotate;
}
}
/**
* Undertow thread properties.
*/
public static class Threads {
/**
* Number of I/O threads to create for the worker. The default is derived from the
* number of available processors.
*/
private Integer io;
/**
* Number of worker threads. The default is 8 times the number of I/O threads.
*/
private Integer worker;
public Integer getIo() {
return this.io;
}
public void setIo(Integer io) {
this.io = io;
}
public Integer getWorker() {
return this.worker;
}
public void setWorker(Integer worker) {
this.worker = worker;
}
}
public static class Options {
/**
* Socket options as defined in org.xnio.Options.
*/
private final Map<String, String> socket = new LinkedHashMap<>();
/**
* Server options as defined in io.undertow.UndertowOptions.
*/
private final Map<String, String> server = new LinkedHashMap<>();
public Map<String, String> getServer() {
return this.server;
}
public Map<String, String> getSocket() {
return this.socket;
}
}
}

View File

@ -39,8 +39,8 @@ public class UndertowWebServerConfiguration {
@Bean
UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
ServerProperties serverProperties, UndertowServerProperties undertowProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties, undertowProperties);
}
@Bean

View File

@ -30,8 +30,7 @@ import org.xnio.Option;
import org.xnio.Options;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.Undertow;
import org.springframework.boot.autoconfigure.web.ServerProperties.Undertow.Accesslog;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowServerProperties.Accesslog;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
@ -63,9 +62,13 @@ public class UndertowWebServerFactoryCustomizer
private final ServerProperties serverProperties;
public UndertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
private final UndertowServerProperties undertowProperties;
public UndertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties,
UndertowServerProperties undertowProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
this.undertowProperties = undertowProperties;
}
@Override
@ -88,33 +91,38 @@ public class UndertowWebServerFactoryCustomizer
private void mapUndertowProperties(ConfigurableUndertowWebServerFactory factory, ServerOptions serverOptions) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
Undertow properties = this.serverProperties.getUndertow();
map.from(properties::getBufferSize).whenNonNull().asInt(DataSize::toBytes).to(factory::setBufferSize);
ServerProperties.Undertow.Threads threadProperties = properties.getThreads();
map.from(this.undertowProperties::getBufferSize)
.whenNonNull()
.asInt(DataSize::toBytes)
.to(factory::setBufferSize);
UndertowServerProperties.Threads threadProperties = this.undertowProperties.getThreads();
map.from(threadProperties::getIo).to(factory::setIoThreads);
map.from(threadProperties::getWorker).to(factory::setWorkerThreads);
map.from(properties::getDirectBuffers).to(factory::setUseDirectBuffers);
map.from(properties::getMaxHttpPostSize)
map.from(this.undertowProperties::getDirectBuffers).to(factory::setUseDirectBuffers);
map.from(this.undertowProperties::getMaxHttpPostSize)
.as(DataSize::toBytes)
.when(this::isPositive)
.to(serverOptions.option(UndertowOptions.MAX_ENTITY_SIZE));
map.from(properties::getMaxParameters).to(serverOptions.option(UndertowOptions.MAX_PARAMETERS));
map.from(properties::getMaxHeaders).to(serverOptions.option(UndertowOptions.MAX_HEADERS));
map.from(properties::getMaxCookies).to(serverOptions.option(UndertowOptions.MAX_COOKIES));
mapSlashProperties(properties, serverOptions);
map.from(properties::isDecodeUrl).to(serverOptions.option(UndertowOptions.DECODE_URL));
map.from(properties::getUrlCharset).as(Charset::name).to(serverOptions.option(UndertowOptions.URL_CHARSET));
map.from(properties::isAlwaysSetKeepAlive).to(serverOptions.option(UndertowOptions.ALWAYS_SET_KEEP_ALIVE));
map.from(properties::getNoRequestTimeout)
map.from(this.undertowProperties::getMaxParameters).to(serverOptions.option(UndertowOptions.MAX_PARAMETERS));
map.from(this.undertowProperties::getMaxHeaders).to(serverOptions.option(UndertowOptions.MAX_HEADERS));
map.from(this.undertowProperties::getMaxCookies).to(serverOptions.option(UndertowOptions.MAX_COOKIES));
mapSlashProperties(this.undertowProperties, serverOptions);
map.from(this.undertowProperties::isDecodeUrl).to(serverOptions.option(UndertowOptions.DECODE_URL));
map.from(this.undertowProperties::getUrlCharset)
.as(Charset::name)
.to(serverOptions.option(UndertowOptions.URL_CHARSET));
map.from(this.undertowProperties::isAlwaysSetKeepAlive)
.to(serverOptions.option(UndertowOptions.ALWAYS_SET_KEEP_ALIVE));
map.from(this.undertowProperties::getNoRequestTimeout)
.asInt(Duration::toMillis)
.to(serverOptions.option(UndertowOptions.NO_REQUEST_TIMEOUT));
map.from(properties.getOptions()::getServer).to(serverOptions.forEach(serverOptions::option));
map.from(this.undertowProperties.getOptions()::getServer).to(serverOptions.forEach(serverOptions::option));
SocketOptions socketOptions = new SocketOptions(factory);
map.from(properties.getOptions()::getSocket).to(socketOptions.forEach(socketOptions::option));
map.from(this.undertowProperties.getOptions()::getSocket).to(socketOptions.forEach(socketOptions::option));
}
@SuppressWarnings({ "deprecation", "removal" })
private void mapSlashProperties(Undertow properties, ServerOptions serverOptions) {
private void mapSlashProperties(UndertowServerProperties properties, ServerOptions serverOptions) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::isAllowEncodedSlash).to(serverOptions.option(UndertowOptions.ALLOW_ENCODED_SLASH));
map.from(properties::getDecodeSlash).to(serverOptions.option(UndertowOptions.DECODE_SLASH));
@ -126,7 +134,7 @@ public class UndertowWebServerFactoryCustomizer
}
private void mapAccessLogProperties(ConfigurableUndertowWebServerFactory factory) {
Accesslog properties = this.serverProperties.getUndertow().getAccesslog();
Accesslog properties = this.undertowProperties.getAccesslog();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::isEnabled).to(factory::setAccessLogEnabled);
map.from(properties::getDir).to(factory::setAccessLogDirectory);

View File

@ -0,0 +1,145 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.server.jetty.JettyServerProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.web.server.jetty.JettyWebServer;
import org.springframework.boot.web.server.servlet.jetty.JettyServletWebServerFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JettyServerProperties}.
*
* @author Andy Wilkinson
*/
class JettyServerPropertiesTests {
private final JettyServerProperties properties = new JettyServerProperties();
@Test
void testCustomizeJettyAcceptors() {
bind("server.jetty.threads.acceptors", "10");
assertThat(this.properties.getThreads().getAcceptors()).isEqualTo(10);
}
@Test
void testCustomizeJettySelectors() {
bind("server.jetty.threads.selectors", "10");
assertThat(this.properties.getThreads().getSelectors()).isEqualTo(10);
}
@Test
void testCustomizeJettyMaxThreads() {
bind("server.jetty.threads.max", "10");
assertThat(this.properties.getThreads().getMax()).isEqualTo(10);
}
@Test
void testCustomizeJettyMinThreads() {
bind("server.jetty.threads.min", "10");
assertThat(this.properties.getThreads().getMin()).isEqualTo(10);
}
@Test
void testCustomizeJettyIdleTimeout() {
bind("server.jetty.threads.idle-timeout", "10s");
assertThat(this.properties.getThreads().getIdleTimeout()).isEqualTo(Duration.ofSeconds(10));
}
@Test
void testCustomizeJettyMaxQueueCapacity() {
bind("server.jetty.threads.max-queue-capacity", "5150");
assertThat(this.properties.getThreads().getMaxQueueCapacity()).isEqualTo(5150);
}
@Test
void testCustomizeJettyAccessLog() {
Map<String, String> map = new HashMap<>();
map.put("server.jetty.accesslog.enabled", "true");
map.put("server.jetty.accesslog.filename", "foo.txt");
map.put("server.jetty.accesslog.file-date-format", "yyyymmdd");
map.put("server.jetty.accesslog.retention-period", "4");
map.put("server.jetty.accesslog.append", "true");
map.put("server.jetty.accesslog.custom-format", "{client}a - %u %t \"%r\" %s %O");
map.put("server.jetty.accesslog.ignore-paths", "/a/path,/b/path");
bind(map);
assertThat(this.properties.getAccesslog().isEnabled()).isTrue();
assertThat(this.properties.getAccesslog().getFilename()).isEqualTo("foo.txt");
assertThat(this.properties.getAccesslog().getFileDateFormat()).isEqualTo("yyyymmdd");
assertThat(this.properties.getAccesslog().getRetentionPeriod()).isEqualTo(4);
assertThat(this.properties.getAccesslog().isAppend()).isTrue();
assertThat(this.properties.getAccesslog().getCustomFormat()).isEqualTo("{client}a - %u %t \"%r\" %s %O");
assertThat(this.properties.getAccesslog().getIgnorePaths()).containsExactly("/a/path", "/b/path");
}
@Test
void jettyThreadPoolPropertyDefaultsShouldMatchServerDefault() {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
Server server = jetty.getServer();
QueuedThreadPool threadPool = (QueuedThreadPool) server.getThreadPool();
int idleTimeout = threadPool.getIdleTimeout();
int maxThreads = threadPool.getMaxThreads();
int minThreads = threadPool.getMinThreads();
assertThat(this.properties.getThreads().getIdleTimeout().toMillis()).isEqualTo(idleTimeout);
assertThat(this.properties.getThreads().getMax()).isEqualTo(maxThreads);
assertThat(this.properties.getThreads().getMin()).isEqualTo(minThreads);
}
@Test
void jettyMaxHttpFormPostSizeMatchesDefault() {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
Server server = jetty.getServer();
assertThat(this.properties.getMaxHttpFormPostSize().toBytes())
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormContentSize());
}
@Test
void jettyMaxFormKeysMatchesDefault() {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
Server server = jetty.getServer();
assertThat(this.properties.getMaxFormKeys())
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormKeys());
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}
private void bind(Map<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("server.jetty", Bindable.ofInstance(this.properties));
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import reactor.netty.http.HttpDecoderSpec;
import org.springframework.boot.autoconfigure.web.server.reactive.netty.NettyServerProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link NettyServerProperties}.
*
* @author Andy Wilkinson
*/
class NettyServerPropertiesTests {
private final NettyServerProperties properties = new NettyServerProperties();
@Test
void testCustomizeNettyIdleTimeout() {
bind("server.netty.idle-timeout", "10s");
assertThat(this.properties.getIdleTimeout()).isEqualTo(Duration.ofSeconds(10));
}
@Test
void testCustomizeNettyMaxKeepAliveRequests() {
bind("server.netty.max-keep-alive-requests", "100");
assertThat(this.properties.getMaxKeepAliveRequests()).isEqualTo(100);
}
@Test
void nettyMaxInitialLineLengthMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.getMaxInitialLineLength().toBytes())
.isEqualTo(HttpDecoderSpec.DEFAULT_MAX_INITIAL_LINE_LENGTH);
}
@Test
void nettyValidateHeadersMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.isValidateHeaders()).isTrue();
}
@Test
void nettyH2cMaxContentLengthMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.getH2cMaxContentLength().toBytes()).isZero();
}
@Test
void nettyInitialBufferSizeMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.getInitialBufferSize().toBytes())
.isEqualTo(HttpDecoderSpec.DEFAULT_INITIAL_BUFFER_SIZE);
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}
private void bind(Map<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("server.netty", Bindable.ofInstance(this.properties));
}
}

View File

@ -17,28 +17,11 @@
package org.springframework.boot.autoconfigure.web;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import io.undertow.UndertowOptions;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.AbstractProtocol;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Test;
import reactor.netty.http.HttpDecoderSpec;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.UseApr;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
@ -46,10 +29,6 @@ import org.springframework.boot.context.properties.source.MapConfigurationProper
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
import org.springframework.boot.web.server.MimeMappings;
import org.springframework.boot.web.server.MimeMappings.Mapping;
import org.springframework.boot.web.server.jetty.JettyWebServer;
import org.springframework.boot.web.server.servlet.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.server.servlet.tomcat.TomcatServletWebServerFactory;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.assertThat;
@ -100,56 +79,6 @@ class ServerPropertiesTests {
assertThat(this.properties.getServerHeader()).isEqualTo("Custom Server");
}
@Test
@SuppressWarnings("removal")
void testTomcatBinding() {
Map<String, String> map = new HashMap<>();
map.put("server.tomcat.accesslog.conditionIf", "foo");
map.put("server.tomcat.accesslog.conditionUnless", "bar");
map.put("server.tomcat.accesslog.pattern", "%h %t '%r' %s %b");
map.put("server.tomcat.accesslog.prefix", "foo");
map.put("server.tomcat.accesslog.suffix", "-bar.log");
map.put("server.tomcat.accesslog.encoding", "UTF-8");
map.put("server.tomcat.accesslog.locale", "en-AU");
map.put("server.tomcat.accesslog.checkExists", "true");
map.put("server.tomcat.accesslog.rotate", "false");
map.put("server.tomcat.accesslog.rename-on-rotate", "true");
map.put("server.tomcat.accesslog.ipv6Canonical", "true");
map.put("server.tomcat.accesslog.request-attributes-enabled", "true");
map.put("server.tomcat.remoteip.protocol-header", "X-Forwarded-Protocol");
map.put("server.tomcat.remoteip.remote-ip-header", "Remote-Ip");
map.put("server.tomcat.remoteip.internal-proxies", "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
map.put("server.tomcat.remoteip.trusted-proxies", "proxy1|proxy2|proxy3");
map.put("server.tomcat.reject-illegal-header", "false");
map.put("server.tomcat.background-processor-delay", "10");
map.put("server.tomcat.relaxed-path-chars", "|,<");
map.put("server.tomcat.relaxed-query-chars", "^ , | ");
map.put("server.tomcat.use-relative-redirects", "true");
bind(map);
ServerProperties.Tomcat tomcat = this.properties.getTomcat();
Accesslog accesslog = tomcat.getAccesslog();
assertThat(accesslog.getConditionIf()).isEqualTo("foo");
assertThat(accesslog.getConditionUnless()).isEqualTo("bar");
assertThat(accesslog.getPattern()).isEqualTo("%h %t '%r' %s %b");
assertThat(accesslog.getPrefix()).isEqualTo("foo");
assertThat(accesslog.getSuffix()).isEqualTo("-bar.log");
assertThat(accesslog.getEncoding()).isEqualTo("UTF-8");
assertThat(accesslog.getLocale()).isEqualTo("en-AU");
assertThat(accesslog.isCheckExists()).isTrue();
assertThat(accesslog.isRotate()).isFalse();
assertThat(accesslog.isRenameOnRotate()).isTrue();
assertThat(accesslog.isIpv6Canonical()).isTrue();
assertThat(accesslog.isRequestAttributesEnabled()).isTrue();
assertThat(tomcat.getRemoteip().getRemoteIpHeader()).isEqualTo("Remote-Ip");
assertThat(tomcat.getRemoteip().getProtocolHeader()).isEqualTo("X-Forwarded-Protocol");
assertThat(tomcat.getRemoteip().getInternalProxies()).isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
assertThat(tomcat.getRemoteip().getTrustedProxies()).isEqualTo("proxy1|proxy2|proxy3");
assertThat(tomcat.getBackgroundProcessorDelay()).hasSeconds(10);
assertThat(tomcat.getRelaxedPathChars()).containsExactly('|', '<');
assertThat(tomcat.getRelaxedQueryChars()).containsExactly('^', '|');
assertThat(tomcat.isUseRelativeRedirects()).isTrue();
}
@Test
void testTrailingSlashOfContextPathIsRemoved() {
bind("server.servlet.context-path", "/foo/");
@ -200,12 +129,6 @@ class ServerPropertiesTests {
.containsExactly(expectedMappings.getAll().toArray(new Mapping[0]));
}
@Test
void testCustomizeTomcatUriEncoding() {
bind("server.tomcat.uri-encoding", "US-ASCII");
assertThat(this.properties.getTomcat().getUriEncoding()).isEqualTo(StandardCharsets.US_ASCII);
}
@Test
void testCustomizeMaxHttpRequestHeaderSize() {
bind("server.max-http-request-header-size", "1MB");
@ -218,311 +141,6 @@ class ServerPropertiesTests {
assertThat(this.properties.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(1));
}
@Test
void testCustomizeTomcatMaxThreads() {
bind("server.tomcat.threads.max", "10");
assertThat(this.properties.getTomcat().getThreads().getMax()).isEqualTo(10);
}
@Test
void testCustomizeTomcatKeepAliveTimeout() {
bind("server.tomcat.keep-alive-timeout", "30s");
assertThat(this.properties.getTomcat().getKeepAliveTimeout()).hasSeconds(30);
}
@Test
void testCustomizeTomcatKeepAliveTimeoutWithInfinite() {
bind("server.tomcat.keep-alive-timeout", "-1");
assertThat(this.properties.getTomcat().getKeepAliveTimeout()).hasMillis(-1);
}
@Test
void testCustomizeTomcatMaxKeepAliveRequests() {
bind("server.tomcat.max-keep-alive-requests", "200");
assertThat(this.properties.getTomcat().getMaxKeepAliveRequests()).isEqualTo(200);
}
@Test
void testCustomizeTomcatMaxKeepAliveRequestsWithInfinite() {
bind("server.tomcat.max-keep-alive-requests", "-1");
assertThat(this.properties.getTomcat().getMaxKeepAliveRequests()).isEqualTo(-1);
}
@Test
void testCustomizeTomcatMaxParameterCount() {
bind("server.tomcat.max-parameter-count", "100");
assertThat(this.properties.getTomcat().getMaxParameterCount()).isEqualTo(100);
}
@Test
void testCustomizeTomcatMinSpareThreads() {
bind("server.tomcat.threads.min-spare", "10");
assertThat(this.properties.getTomcat().getThreads().getMinSpare()).isEqualTo(10);
}
@Test
void testCustomizeJettyAcceptors() {
bind("server.jetty.threads.acceptors", "10");
assertThat(this.properties.getJetty().getThreads().getAcceptors()).isEqualTo(10);
}
@Test
void testCustomizeJettySelectors() {
bind("server.jetty.threads.selectors", "10");
assertThat(this.properties.getJetty().getThreads().getSelectors()).isEqualTo(10);
}
@Test
void testCustomizeJettyMaxThreads() {
bind("server.jetty.threads.max", "10");
assertThat(this.properties.getJetty().getThreads().getMax()).isEqualTo(10);
}
@Test
void testCustomizeJettyMinThreads() {
bind("server.jetty.threads.min", "10");
assertThat(this.properties.getJetty().getThreads().getMin()).isEqualTo(10);
}
@Test
void testCustomizeJettyIdleTimeout() {
bind("server.jetty.threads.idle-timeout", "10s");
assertThat(this.properties.getJetty().getThreads().getIdleTimeout()).isEqualTo(Duration.ofSeconds(10));
}
@Test
void testCustomizeJettyMaxQueueCapacity() {
bind("server.jetty.threads.max-queue-capacity", "5150");
assertThat(this.properties.getJetty().getThreads().getMaxQueueCapacity()).isEqualTo(5150);
}
@Test
void testCustomizeUndertowServerOption() {
bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE", "true");
assertThat(this.properties.getUndertow().getOptions().getServer()).containsEntry("ALWAYS_SET_KEEP_ALIVE",
"true");
}
@Test
void testCustomizeUndertowSocketOption() {
bind("server.undertow.options.socket.ALWAYS_SET_KEEP_ALIVE", "true");
assertThat(this.properties.getUndertow().getOptions().getSocket()).containsEntry("ALWAYS_SET_KEEP_ALIVE",
"true");
}
@Test
void testCustomizeUndertowIoThreads() {
bind("server.undertow.threads.io", "4");
assertThat(this.properties.getUndertow().getThreads().getIo()).isEqualTo(4);
}
@Test
void testCustomizeUndertowWorkerThreads() {
bind("server.undertow.threads.worker", "10");
assertThat(this.properties.getUndertow().getThreads().getWorker()).isEqualTo(10);
}
@Test
void testCustomizeJettyAccessLog() {
Map<String, String> map = new HashMap<>();
map.put("server.jetty.accesslog.enabled", "true");
map.put("server.jetty.accesslog.filename", "foo.txt");
map.put("server.jetty.accesslog.file-date-format", "yyyymmdd");
map.put("server.jetty.accesslog.retention-period", "4");
map.put("server.jetty.accesslog.append", "true");
map.put("server.jetty.accesslog.custom-format", "{client}a - %u %t \"%r\" %s %O");
map.put("server.jetty.accesslog.ignore-paths", "/a/path,/b/path");
bind(map);
ServerProperties.Jetty jetty = this.properties.getJetty();
assertThat(jetty.getAccesslog().isEnabled()).isTrue();
assertThat(jetty.getAccesslog().getFilename()).isEqualTo("foo.txt");
assertThat(jetty.getAccesslog().getFileDateFormat()).isEqualTo("yyyymmdd");
assertThat(jetty.getAccesslog().getRetentionPeriod()).isEqualTo(4);
assertThat(jetty.getAccesslog().isAppend()).isTrue();
assertThat(jetty.getAccesslog().getCustomFormat()).isEqualTo("{client}a - %u %t \"%r\" %s %O");
assertThat(jetty.getAccesslog().getIgnorePaths()).containsExactly("/a/path", "/b/path");
}
@Test
void testCustomizeNettyIdleTimeout() {
bind("server.netty.idle-timeout", "10s");
assertThat(this.properties.getNetty().getIdleTimeout()).isEqualTo(Duration.ofSeconds(10));
}
@Test
void testCustomizeNettyMaxKeepAliveRequests() {
bind("server.netty.max-keep-alive-requests", "100");
assertThat(this.properties.getNetty().getMaxKeepAliveRequests()).isEqualTo(100);
}
@Test
void tomcatAcceptCountMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getAcceptCount()).isEqualTo(getDefaultProtocol().getAcceptCount());
}
@Test
void tomcatProcessorCacheMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getProcessorCache()).isEqualTo(getDefaultProtocol().getProcessorCache());
}
@Test
void tomcatMaxConnectionsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getMaxConnections()).isEqualTo(getDefaultProtocol().getMaxConnections());
}
@Test
void tomcatMaxThreadsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getThreads().getMax()).isEqualTo(getDefaultProtocol().getMaxThreads());
}
@Test
void tomcatMinSpareThreadsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getThreads().getMinSpare())
.isEqualTo(getDefaultProtocol().getMinSpareThreads());
}
@Test
void tomcatMaxHttpPostSizeMatchesConnectorDefault() {
assertThat(this.properties.getTomcat().getMaxHttpFormPostSize().toBytes())
.isEqualTo(getDefaultConnector().getMaxPostSize());
}
@Test
void tomcatMaxParameterCountMatchesConnectorDefault() {
assertThat(this.properties.getTomcat().getMaxParameterCount())
.isEqualTo(getDefaultConnector().getMaxParameterCount());
}
@Test
void tomcatBackgroundProcessorDelayMatchesEngineDefault() {
assertThat(this.properties.getTomcat().getBackgroundProcessorDelay())
.hasSeconds((new StandardEngine().getBackgroundProcessorDelay()));
}
@Test
void tomcatMaxHttpFormPostSizeMatchesConnectorDefault() {
assertThat(this.properties.getTomcat().getMaxHttpFormPostSize().toBytes())
.isEqualTo(getDefaultConnector().getMaxPostSize());
}
@Test
void tomcatUriEncodingMatchesConnectorDefault() {
assertThat(this.properties.getTomcat().getUriEncoding().name())
.isEqualTo(getDefaultConnector().getURIEncoding());
}
@Test
void tomcatRedirectContextRootMatchesDefault() {
assertThat(this.properties.getTomcat().getRedirectContextRoot())
.isEqualTo(new StandardContext().getMapperContextRootRedirectEnabled());
}
@Test
void tomcatAccessLogRenameOnRotateMatchesDefault() {
assertThat(this.properties.getTomcat().getAccesslog().isRenameOnRotate())
.isEqualTo(new AccessLogValve().isRenameOnRotate());
}
@Test
void tomcatAccessLogRequestAttributesEnabledMatchesDefault() {
assertThat(this.properties.getTomcat().getAccesslog().isRequestAttributesEnabled())
.isEqualTo(new AccessLogValve().getRequestAttributesEnabled());
}
@Test
void tomcatInternalProxiesMatchesDefault() {
assertThat(this.properties.getTomcat().getRemoteip().getInternalProxies())
.isEqualTo(new RemoteIpValve().getInternalProxies());
}
@Test
void tomcatUseRelativeRedirectsDefaultsToFalse() {
assertThat(this.properties.getTomcat().isUseRelativeRedirects()).isFalse();
}
@Test
void tomcatMaxKeepAliveRequestsDefault() throws Exception {
AbstractEndpoint<?, ?> endpoint = (AbstractEndpoint<?, ?>) ReflectionTestUtils.getField(getDefaultProtocol(),
"endpoint");
int defaultMaxKeepAliveRequests = (int) ReflectionTestUtils.getField(endpoint, "maxKeepAliveRequests");
assertThat(this.properties.getTomcat().getMaxKeepAliveRequests()).isEqualTo(defaultMaxKeepAliveRequests);
}
@Test
void jettyThreadPoolPropertyDefaultsShouldMatchServerDefault() {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
Server server = jetty.getServer();
QueuedThreadPool threadPool = (QueuedThreadPool) server.getThreadPool();
int idleTimeout = threadPool.getIdleTimeout();
int maxThreads = threadPool.getMaxThreads();
int minThreads = threadPool.getMinThreads();
assertThat(this.properties.getJetty().getThreads().getIdleTimeout().toMillis()).isEqualTo(idleTimeout);
assertThat(this.properties.getJetty().getThreads().getMax()).isEqualTo(maxThreads);
assertThat(this.properties.getJetty().getThreads().getMin()).isEqualTo(minThreads);
}
@Test
void jettyMaxHttpFormPostSizeMatchesDefault() {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
Server server = jetty.getServer();
assertThat(this.properties.getJetty().getMaxHttpFormPostSize().toBytes())
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormContentSize());
}
@Test
void jettyMaxFormKeysMatchesDefault() {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
Server server = jetty.getServer();
assertThat(this.properties.getJetty().getMaxFormKeys())
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormKeys());
}
@Test
void undertowMaxHttpPostSizeMatchesDefault() {
assertThat(this.properties.getUndertow().getMaxHttpPostSize().toBytes())
.isEqualTo(UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
}
@Test
void nettyMaxInitialLineLengthMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.getNetty().getMaxInitialLineLength().toBytes())
.isEqualTo(HttpDecoderSpec.DEFAULT_MAX_INITIAL_LINE_LENGTH);
}
@Test
void nettyValidateHeadersMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.getNetty().isValidateHeaders()).isTrue();
}
@Test
void nettyH2cMaxContentLengthMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.getNetty().getH2cMaxContentLength().toBytes()).isZero();
}
@Test
void nettyInitialBufferSizeMatchesHttpDecoderSpecDefault() {
assertThat(this.properties.getNetty().getInitialBufferSize().toBytes())
.isEqualTo(HttpDecoderSpec.DEFAULT_INITIAL_BUFFER_SIZE);
}
@Test
void shouldDefaultAprToNever() {
assertThat(this.properties.getTomcat().getUseApr()).isEqualTo(UseApr.NEVER);
}
private Connector getDefaultConnector() {
return new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
}
private AbstractProtocol<?> getDefaultProtocol() throws Exception {
return (AbstractProtocol<?>) Class.forName(TomcatServletWebServerFactory.DEFAULT_PROTOCOL)
.getDeclaredConstructor()
.newInstance();
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}

View File

@ -0,0 +1,264 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.AbstractProtocol;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties.Accesslog;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties.UseApr;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.web.server.tomcat.TomcatWebServerFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TomcatServerProperties}.
*
* @author Andy Wilkinson
*/
class TomcatServerPropertiesTests {
private final TomcatServerProperties properties = new TomcatServerProperties();
@Test
void testTomcatBinding() {
Map<String, String> map = new HashMap<>();
map.put("server.tomcat.accesslog.conditionIf", "foo");
map.put("server.tomcat.accesslog.conditionUnless", "bar");
map.put("server.tomcat.accesslog.pattern", "%h %t '%r' %s %b");
map.put("server.tomcat.accesslog.prefix", "foo");
map.put("server.tomcat.accesslog.suffix", "-bar.log");
map.put("server.tomcat.accesslog.encoding", "UTF-8");
map.put("server.tomcat.accesslog.locale", "en-AU");
map.put("server.tomcat.accesslog.checkExists", "true");
map.put("server.tomcat.accesslog.rotate", "false");
map.put("server.tomcat.accesslog.rename-on-rotate", "true");
map.put("server.tomcat.accesslog.ipv6Canonical", "true");
map.put("server.tomcat.accesslog.request-attributes-enabled", "true");
map.put("server.tomcat.remoteip.protocol-header", "X-Forwarded-Protocol");
map.put("server.tomcat.remoteip.remote-ip-header", "Remote-Ip");
map.put("server.tomcat.remoteip.internal-proxies", "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
map.put("server.tomcat.remoteip.trusted-proxies", "proxy1|proxy2|proxy3");
map.put("server.tomcat.reject-illegal-header", "false");
map.put("server.tomcat.background-processor-delay", "10");
map.put("server.tomcat.relaxed-path-chars", "|,<");
map.put("server.tomcat.relaxed-query-chars", "^ , | ");
map.put("server.tomcat.use-relative-redirects", "true");
bind(map);
Accesslog accesslog = this.properties.getAccesslog();
assertThat(accesslog.getConditionIf()).isEqualTo("foo");
assertThat(accesslog.getConditionUnless()).isEqualTo("bar");
assertThat(accesslog.getPattern()).isEqualTo("%h %t '%r' %s %b");
assertThat(accesslog.getPrefix()).isEqualTo("foo");
assertThat(accesslog.getSuffix()).isEqualTo("-bar.log");
assertThat(accesslog.getEncoding()).isEqualTo("UTF-8");
assertThat(accesslog.getLocale()).isEqualTo("en-AU");
assertThat(accesslog.isCheckExists()).isTrue();
assertThat(accesslog.isRotate()).isFalse();
assertThat(accesslog.isRenameOnRotate()).isTrue();
assertThat(accesslog.isIpv6Canonical()).isTrue();
assertThat(accesslog.isRequestAttributesEnabled()).isTrue();
assertThat(this.properties.getRemoteip().getRemoteIpHeader()).isEqualTo("Remote-Ip");
assertThat(this.properties.getRemoteip().getProtocolHeader()).isEqualTo("X-Forwarded-Protocol");
assertThat(this.properties.getRemoteip().getInternalProxies()).isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
assertThat(this.properties.getRemoteip().getTrustedProxies()).isEqualTo("proxy1|proxy2|proxy3");
assertThat(this.properties.getBackgroundProcessorDelay()).hasSeconds(10);
assertThat(this.properties.getRelaxedPathChars()).containsExactly('|', '<');
assertThat(this.properties.getRelaxedQueryChars()).containsExactly('^', '|');
assertThat(this.properties.isUseRelativeRedirects()).isTrue();
}
@Test
void testCustomizeTomcatUriEncoding() {
bind("server.tomcat.uri-encoding", "US-ASCII");
assertThat(this.properties.getUriEncoding()).isEqualTo(StandardCharsets.US_ASCII);
}
@Test
void testCustomizeTomcatMaxThreads() {
bind("server.tomcat.threads.max", "10");
assertThat(this.properties.getThreads().getMax()).isEqualTo(10);
}
@Test
void testCustomizeTomcatKeepAliveTimeout() {
bind("server.tomcat.keep-alive-timeout", "30s");
assertThat(this.properties.getKeepAliveTimeout()).hasSeconds(30);
}
@Test
void testCustomizeTomcatKeepAliveTimeoutWithInfinite() {
bind("server.tomcat.keep-alive-timeout", "-1");
assertThat(this.properties.getKeepAliveTimeout()).hasMillis(-1);
}
@Test
void testCustomizeTomcatMaxKeepAliveRequests() {
bind("server.tomcat.max-keep-alive-requests", "200");
assertThat(this.properties.getMaxKeepAliveRequests()).isEqualTo(200);
}
@Test
void testCustomizeTomcatMaxKeepAliveRequestsWithInfinite() {
bind("server.tomcat.max-keep-alive-requests", "-1");
assertThat(this.properties.getMaxKeepAliveRequests()).isEqualTo(-1);
}
@Test
void testCustomizeTomcatMaxParameterCount() {
bind("server.tomcat.max-parameter-count", "100");
assertThat(this.properties.getMaxParameterCount()).isEqualTo(100);
}
@Test
void testCustomizeTomcatMinSpareThreads() {
bind("server.tomcat.threads.min-spare", "10");
assertThat(this.properties.getThreads().getMinSpare()).isEqualTo(10);
}
@Test
void tomcatAcceptCountMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getAcceptCount()).isEqualTo(getDefaultProtocol().getAcceptCount());
}
@Test
void tomcatProcessorCacheMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getProcessorCache()).isEqualTo(getDefaultProtocol().getProcessorCache());
}
@Test
void tomcatMaxConnectionsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getMaxConnections()).isEqualTo(getDefaultProtocol().getMaxConnections());
}
@Test
void tomcatMaxThreadsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getThreads().getMax()).isEqualTo(getDefaultProtocol().getMaxThreads());
}
@Test
void tomcatMinSpareThreadsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getThreads().getMinSpare()).isEqualTo(getDefaultProtocol().getMinSpareThreads());
}
@Test
void tomcatMaxHttpPostSizeMatchesConnectorDefault() {
assertThat(this.properties.getMaxHttpFormPostSize().toBytes())
.isEqualTo(getDefaultConnector().getMaxPostSize());
}
@Test
void tomcatMaxParameterCountMatchesConnectorDefault() {
assertThat(this.properties.getMaxParameterCount()).isEqualTo(getDefaultConnector().getMaxParameterCount());
}
@Test
void tomcatBackgroundProcessorDelayMatchesEngineDefault() {
assertThat(this.properties.getBackgroundProcessorDelay())
.hasSeconds((new StandardEngine().getBackgroundProcessorDelay()));
}
@Test
void tomcatMaxHttpFormPostSizeMatchesConnectorDefault() {
assertThat(this.properties.getMaxHttpFormPostSize().toBytes())
.isEqualTo(getDefaultConnector().getMaxPostSize());
}
@Test
void tomcatUriEncodingMatchesConnectorDefault() {
assertThat(this.properties.getUriEncoding().name()).isEqualTo(getDefaultConnector().getURIEncoding());
}
@Test
void tomcatRedirectContextRootMatchesDefault() {
assertThat(this.properties.getRedirectContextRoot())
.isEqualTo(new StandardContext().getMapperContextRootRedirectEnabled());
}
@Test
void tomcatAccessLogRenameOnRotateMatchesDefault() {
assertThat(this.properties.getAccesslog().isRenameOnRotate())
.isEqualTo(new AccessLogValve().isRenameOnRotate());
}
@Test
void tomcatAccessLogRequestAttributesEnabledMatchesDefault() {
assertThat(this.properties.getAccesslog().isRequestAttributesEnabled())
.isEqualTo(new AccessLogValve().getRequestAttributesEnabled());
}
@Test
void tomcatInternalProxiesMatchesDefault() {
assertThat(this.properties.getRemoteip().getInternalProxies())
.isEqualTo(new RemoteIpValve().getInternalProxies());
}
@Test
void tomcatUseRelativeRedirectsDefaultsToFalse() {
assertThat(this.properties.isUseRelativeRedirects()).isFalse();
}
@Test
void tomcatMaxKeepAliveRequestsDefault() throws Exception {
AbstractEndpoint<?, ?> endpoint = (AbstractEndpoint<?, ?>) ReflectionTestUtils.getField(getDefaultProtocol(),
"endpoint");
int defaultMaxKeepAliveRequests = (int) ReflectionTestUtils.getField(endpoint, "maxKeepAliveRequests");
assertThat(this.properties.getMaxKeepAliveRequests()).isEqualTo(defaultMaxKeepAliveRequests);
}
@Test
void shouldDefaultAprToNever() {
assertThat(this.properties.getUseApr()).isEqualTo(UseApr.NEVER);
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}
private void bind(Map<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("server.tomcat", Bindable.ofInstance(this.properties));
}
private Connector getDefaultConnector() {
return new Connector(TomcatWebServerFactory.DEFAULT_PROTOCOL);
}
private AbstractProtocol<?> getDefaultProtocol() throws Exception {
return (AbstractProtocol<?>) Class.forName(TomcatWebServerFactory.DEFAULT_PROTOCOL)
.getDeclaredConstructor()
.newInstance();
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.util.Collections;
import java.util.Map;
import io.undertow.UndertowOptions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowServerProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link UndertowServerProperties}.
*
* @author Andy Wilkinson
*/
class UndertowServerPropertiesTests {
private final UndertowServerProperties properties = new UndertowServerProperties();
@Test
void testCustomizeUndertowServerOption() {
bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE", "true");
assertThat(this.properties.getOptions().getServer()).containsEntry("ALWAYS_SET_KEEP_ALIVE", "true");
}
@Test
void testCustomizeUndertowSocketOption() {
bind("server.undertow.options.socket.ALWAYS_SET_KEEP_ALIVE", "true");
assertThat(this.properties.getOptions().getSocket()).containsEntry("ALWAYS_SET_KEEP_ALIVE", "true");
}
@Test
void testCustomizeUndertowIoThreads() {
bind("server.undertow.threads.io", "4");
assertThat(this.properties.getThreads().getIo()).isEqualTo(4);
}
@Test
void testCustomizeUndertowWorkerThreads() {
bind("server.undertow.threads.worker", "10");
assertThat(this.properties.getThreads().getWorker()).isEqualTo(10);
}
@Test
void undertowMaxHttpPostSizeMatchesDefault() {
assertThat(this.properties.getMaxHttpPostSize().toBytes()).isEqualTo(UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}
private void bind(Map<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("server.undertow", Bindable.ofInstance(this.properties));
}
}

View File

@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.server.jetty.ConfigurableJettyWebServerFactory;
import static org.assertj.core.api.Assertions.assertThat;
@ -45,7 +44,7 @@ class JettyVirtualThreadsWebServerFactoryCustomizerTests {
@Test
@EnabledForJreRange(min = JRE.JAVA_21)
void shouldConfigureVirtualThreads() {
ServerProperties properties = new ServerProperties();
JettyServerProperties properties = new JettyServerProperties();
JettyVirtualThreadsWebServerFactoryCustomizer customizer = new JettyVirtualThreadsWebServerFactoryCustomizer(
properties);
ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class);

View File

@ -43,11 +43,9 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.ForwardHeadersStrategy;
import org.springframework.boot.autoconfigure.web.ServerProperties.Jetty;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
import org.springframework.boot.web.server.jetty.ConfigurableJettyWebServerFactory;
import org.springframework.boot.web.server.jetty.JettyWebServer;
import org.springframework.boot.web.server.servlet.jetty.JettyServletWebServerFactory;
@ -66,21 +64,22 @@ import static org.mockito.Mockito.mock;
* @author Phillip Webb
* @author HaiTao Zhang
*/
@DirtiesUrlFactories
// @DirtiesUrlFactories
class JettyWebServerFactoryCustomizerTests {
private MockEnvironment environment;
private final MockEnvironment environment = new MockEnvironment();
private ServerProperties serverProperties;
private final ServerProperties serverProperties = new ServerProperties();
private final JettyServerProperties jettyProperties = new JettyServerProperties();
private JettyWebServerFactoryCustomizer customizer;
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.serverProperties = new ServerProperties();
ConfigurationPropertySources.attach(this.environment);
this.customizer = new JettyWebServerFactoryCustomizer(this.environment, this.serverProperties);
this.customizer = new JettyWebServerFactoryCustomizer(this.environment, this.serverProperties,
this.jettyProperties);
}
@Test
@ -235,7 +234,7 @@ class JettyWebServerFactoryCustomizerTests {
private void assertDefaultThreadPoolSettings(ThreadPool threadPool) {
assertThat(threadPool).isInstanceOf(QueuedThreadPool.class);
QueuedThreadPool queuedThreadPool = (QueuedThreadPool) threadPool;
Jetty defaultProperties = new Jetty();
JettyServerProperties defaultProperties = new JettyServerProperties();
assertThat(queuedThreadPool.getMinThreads()).isEqualTo(defaultProperties.getThreads().getMin());
assertThat(queuedThreadPool.getMaxThreads()).isEqualTo(defaultProperties.getThreads().getMax());
assertThat(queuedThreadPool.getIdleTimeout())
@ -384,8 +383,9 @@ class JettyWebServerFactoryCustomizerTests {
private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",
Bindable.ofInstance(this.serverProperties));
Binder binder = new Binder(ConfigurationPropertySources.get(this.environment));
binder.bind("server", Bindable.ofInstance(this.serverProperties));
binder.bind("server.jetty", Bindable.ofInstance(this.jettyProperties));
}
private JettyWebServer customizeAndGetServer() {

View File

@ -54,9 +54,11 @@ import static org.mockito.Mockito.times;
@ExtendWith(MockitoExtension.class)
class NettyReactiveWebServerFactoryCustomizerTests {
private MockEnvironment environment;
private final MockEnvironment environment = new MockEnvironment();
private ServerProperties serverProperties;
private final ServerProperties serverProperties = new ServerProperties();
private final NettyServerProperties nettyProperties = new NettyServerProperties();
private NettyReactiveWebServerFactoryCustomizer customizer;
@ -65,10 +67,9 @@ class NettyReactiveWebServerFactoryCustomizerTests {
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.serverProperties = new ServerProperties();
ConfigurationPropertySources.attach(this.environment);
this.customizer = new NettyReactiveWebServerFactoryCustomizer(this.environment, this.serverProperties);
this.customizer = new NettyReactiveWebServerFactoryCustomizer(this.environment, this.serverProperties,
this.nettyProperties);
}
@Test
@ -105,7 +106,7 @@ class NettyReactiveWebServerFactoryCustomizerTests {
@Test
void setConnectionTimeout() {
this.serverProperties.getNetty().setConnectionTimeout(Duration.ofSeconds(1));
this.nettyProperties.setConnectionTimeout(Duration.ofSeconds(1));
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
this.customizer.customize(factory);
verifyConnectionTimeout(factory, 1000);
@ -113,7 +114,7 @@ class NettyReactiveWebServerFactoryCustomizerTests {
@Test
void setIdleTimeout() {
this.serverProperties.getNetty().setIdleTimeout(Duration.ofSeconds(1));
this.nettyProperties.setIdleTimeout(Duration.ofSeconds(1));
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
this.customizer.customize(factory);
verifyIdleTimeout(factory, Duration.ofSeconds(1));
@ -121,7 +122,7 @@ class NettyReactiveWebServerFactoryCustomizerTests {
@Test
void setMaxKeepAliveRequests() {
this.serverProperties.getNetty().setMaxKeepAliveRequests(100);
this.nettyProperties.setMaxKeepAliveRequests(100);
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
this.customizer.customize(factory);
verifyMaxKeepAliveRequests(factory, 100);
@ -139,7 +140,7 @@ class NettyReactiveWebServerFactoryCustomizerTests {
@Test
void configureHttpRequestDecoder() {
ServerProperties.Netty nettyProperties = this.serverProperties.getNetty();
NettyServerProperties nettyProperties = this.nettyProperties;
this.serverProperties.setMaxHttpRequestHeaderSize(DataSize.ofKilobytes(24));
nettyProperties.setValidateHeaders(false);
nettyProperties.setInitialBufferSize(DataSize.ofBytes(512));

View File

@ -164,14 +164,6 @@ class ServletWebServerFactoryCustomizerTests {
then(factory).should().setDisplayName("MyBootApp");
}
@Test
void testCustomizeTomcatMinSpareThreads() {
Map<String, String> map = new HashMap<>();
map.put("server.tomcat.threads.min-spare", "10");
bindProperties(map);
assertThat(this.properties.getTomcat().getThreads().getMinSpare()).isEqualTo(10);
}
@Test
void sessionStoreDir() {
Map<String, String> map = new HashMap<>();

View File

@ -20,7 +20,7 @@ import org.apache.catalina.Context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.server.tomcat.TomcatServerProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
@ -38,18 +38,17 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class TomcatServletWebServerFactoryCustomizerTests {
private final TomcatServerProperties tomcatProperties = new TomcatServerProperties();
private TomcatServletWebServerFactoryCustomizer customizer;
private MockEnvironment environment;
private ServerProperties serverProperties;
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.serverProperties = new ServerProperties();
ConfigurationPropertySources.attach(this.environment);
this.customizer = new TomcatServletWebServerFactoryCustomizer(this.serverProperties);
this.customizer = new TomcatServletWebServerFactoryCustomizer(this.tomcatProperties);
}
@Test
@ -74,8 +73,7 @@ class TomcatServletWebServerFactoryCustomizerTests {
@Test
void redirectContextRootCanBeConfigured() {
bind("server.tomcat.redirect-context-root=false");
ServerProperties.Tomcat tomcat = this.serverProperties.getTomcat();
assertThat(tomcat.getRedirectContextRoot()).isFalse();
assertThat(this.tomcatProperties.getRedirectContextRoot()).isFalse();
TomcatWebServer server = customizeAndGetServer();
Context context = (Context) server.getTomcat().getHost().findChildren()[0];
assertThat(context.getMapperContextRootRedirectEnabled()).isFalse();
@ -84,7 +82,7 @@ class TomcatServletWebServerFactoryCustomizerTests {
@Test
void useRelativeRedirectsCanBeConfigured() {
bind("server.tomcat.use-relative-redirects=true");
assertThat(this.serverProperties.getTomcat().isUseRelativeRedirects()).isTrue();
assertThat(this.tomcatProperties.isUseRelativeRedirects()).isTrue();
TomcatWebServer server = customizeAndGetServer();
Context context = (Context) server.getTomcat().getHost().findChildren()[0];
assertThat(context.getUseRelativeRedirects()).isTrue();
@ -92,8 +90,8 @@ class TomcatServletWebServerFactoryCustomizerTests {
private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",
Bindable.ofInstance(this.serverProperties));
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server.tomcat",
Bindable.ofInstance(this.tomcatProperties));
}
private TomcatWebServer customizeAndGetServer() {

View File

@ -18,7 +18,7 @@ package org.springframework.boot.autoconfigure.web.server.servlet.undertow;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.server.undertow.UndertowServerProperties;
import org.springframework.boot.web.server.servlet.undertow.UndertowServletWebServerFactory;
import static org.assertj.core.api.Assertions.assertThat;
@ -34,9 +34,9 @@ class UndertowServletWebServerFactoryCustomizerTests {
void eagerFilterInitCanBeDisabled() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory(0);
assertThat(factory.isEagerFilterInit()).isTrue();
ServerProperties serverProperties = new ServerProperties();
serverProperties.getUndertow().setEagerFilterInit(false);
new UndertowServletWebServerFactoryCustomizer(serverProperties).customize(factory);
UndertowServerProperties undertowProperties = new UndertowServerProperties();
undertowProperties.setEagerFilterInit(false);
new UndertowServletWebServerFactoryCustomizer(undertowProperties).customize(factory);
assertThat(factory.isEagerFilterInit()).isFalse();
}
@ -44,9 +44,9 @@ class UndertowServletWebServerFactoryCustomizerTests {
void preservePathOnForwardCanBeEnabled() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory(0);
assertThat(factory.isPreservePathOnForward()).isFalse();
ServerProperties serverProperties = new ServerProperties();
serverProperties.getUndertow().setPreservePathOnForward(true);
new UndertowServletWebServerFactoryCustomizer(serverProperties).customize(factory);
UndertowServerProperties undertowProperties = new UndertowServerProperties();
undertowProperties.setPreservePathOnForward(true);
new UndertowServletWebServerFactoryCustomizer(undertowProperties).customize(factory);
assertThat(factory.isPreservePathOnForward()).isTrue();
}

View File

@ -62,18 +62,19 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class TomcatWebServerFactoryCustomizerTests {
private MockEnvironment environment;
private final MockEnvironment environment = new MockEnvironment();
private ServerProperties serverProperties;
private final ServerProperties serverProperties = new ServerProperties();
private final TomcatServerProperties tomcatProperties = new TomcatServerProperties();
private TomcatWebServerFactoryCustomizer customizer;
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.serverProperties = new ServerProperties();
ConfigurationPropertySources.attach(this.environment);
this.customizer = new TomcatWebServerFactoryCustomizer(this.environment, this.serverProperties);
this.customizer = new TomcatWebServerFactoryCustomizer(this.environment, this.serverProperties,
this.tomcatProperties);
}
@Test
@ -81,7 +82,7 @@ class TomcatWebServerFactoryCustomizerTests {
customizeAndRunServer((server) -> assertThat(
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
.getMaxSwallowSize())
.isEqualTo(this.serverProperties.getTomcat().getMaxSwallowSize().toBytes()));
.isEqualTo(this.tomcatProperties.getMaxSwallowSize().toBytes()));
}
@Test
@ -422,7 +423,7 @@ class TomcatWebServerFactoryCustomizerTests {
@Test
void testCustomizeMinSpareThreads() {
bind("server.tomcat.threads.min-spare=10");
assertThat(this.serverProperties.getTomcat().getThreads().getMinSpare()).isEqualTo(10);
assertThat(this.tomcatProperties.getThreads().getMinSpare()).isEqualTo(10);
}
@Test
@ -482,7 +483,7 @@ class TomcatWebServerFactoryCustomizerTests {
bind("server.tomcat.accesslog.enabled=true");
TomcatServletWebServerFactory factory = customizeAndGetFactory();
assertThat(((AccessLogValve) factory.getEngineValves().iterator().next()).getMaxDays())
.isEqualTo(this.serverProperties.getTomcat().getAccesslog().getMaxDays());
.isEqualTo(this.tomcatProperties.getAccesslog().getMaxDays());
}
@Test
@ -589,8 +590,9 @@ class TomcatWebServerFactoryCustomizerTests {
private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",
Bindable.ofInstance(this.serverProperties));
Binder binder = new Binder(ConfigurationPropertySources.get(this.environment));
binder.bind("server", Bindable.ofInstance(this.serverProperties));
binder.bind("server.tomcat", Bindable.ofInstance(this.tomcatProperties));
}
private void customizeAndRunServer(Consumer<TomcatWebServer> consumer) {

View File

@ -56,18 +56,19 @@ import static org.mockito.Mockito.mock;
*/
class UndertowWebServerFactoryCustomizerTests {
private MockEnvironment environment;
private final MockEnvironment environment = new MockEnvironment();
private ServerProperties serverProperties;
private final ServerProperties serverProperties = new ServerProperties();
private final UndertowServerProperties undertowProperties = new UndertowServerProperties();
private UndertowWebServerFactoryCustomizer customizer;
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.serverProperties = new ServerProperties();
ConfigurationPropertySources.attach(this.environment);
this.customizer = new UndertowWebServerFactoryCustomizer(this.environment, this.serverProperties);
this.customizer = new UndertowWebServerFactoryCustomizer(this.environment, this.serverProperties,
this.undertowProperties);
}
@Test
@ -266,8 +267,9 @@ class UndertowWebServerFactoryCustomizerTests {
private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",
Bindable.ofInstance(this.serverProperties));
Binder binder = new Binder(ConfigurationPropertySources.get(this.environment));
binder.bind("server", Bindable.ofInstance(this.serverProperties));
binder.bind("server.undertow", Bindable.ofInstance(this.undertowProperties));
}
}