From 90dc10cdeeb6bf6b95623faf55bff1e79c6f5657 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 5 Feb 2025 08:43:39 +0000 Subject: [PATCH] Move server-specific properties out of ServerProperties Issue: 44064 --- .../autoconfigure/web/ServerProperties.java | 1601 ----------------- .../server/jetty/JettyServerProperties.java | 359 ++++ .../web/server/jetty/JettyThreadPool.java | 6 +- ...tualThreadsWebServerFactoryCustomizer.java | 25 +- .../jetty/JettyWebServerConfiguration.java | 13 +- .../JettyWebServerFactoryCustomizer.java | 31 +- ...ttyReactiveWebServerAutoConfiguration.java | 3 + ...ttyReactiveWebServerAutoConfiguration.java | 6 +- ...ttyReactiveWebServerFactoryCustomizer.java | 22 +- .../reactive/netty/NettyServerProperties.java | 147 ++ ...catReactiveWebServerAutoConfiguration.java | 8 +- ...catReactiveWebServerFactoryCustomizer.java | 39 +- ...towReactiveWebServerAutoConfiguration.java | 3 + ...ettyServletWebServerAutoConfiguration.java | 3 + ...mcatServletWebServerAutoConfiguration.java | 16 +- ...mcatServletWebServerFactoryCustomizer.java | 26 +- ...rtowServletWebServerAutoConfiguration.java | 8 +- ...rtowServletWebServerFactoryCustomizer.java | 11 +- .../server/tomcat/TomcatServerProperties.java | 840 +++++++++ .../tomcat/TomcatWebServerConfiguration.java | 4 +- .../TomcatWebServerFactoryCustomizer.java | 58 +- .../undertow/UndertowServerProperties.java | 425 +++++ .../UndertowWebServerConfiguration.java | 4 +- .../UndertowWebServerFactoryCustomizer.java | 48 +- .../web/JettyServerPropertiesTests.java | 145 ++ .../web/NettyServerPropertiesTests.java | 86 + .../web/ServerPropertiesTests.java | 382 ---- .../web/TomcatServerPropertiesTests.java | 264 +++ .../web/UndertowServerPropertiesTests.java | 80 + ...hreadsWebServerFactoryCustomizerTests.java | 3 +- .../JettyWebServerFactoryCustomizerTests.java | 22 +- ...activeWebServerFactoryCustomizerTests.java | 19 +- ...ervletWebServerFactoryCustomizerTests.java | 8 - ...ervletWebServerFactoryCustomizerTests.java | 18 +- ...ervletWebServerFactoryCustomizerTests.java | 14 +- ...TomcatWebServerFactoryCustomizerTests.java | 22 +- ...dertowWebServerFactoryCustomizerTests.java | 16 +- 37 files changed, 2586 insertions(+), 2199 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyServerProperties.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyServerProperties.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatServerProperties.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowServerProperties.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/JettyServerPropertiesTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/NettyServerPropertiesTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/TomcatServerPropertiesTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/UndertowServerPropertiesTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 40248069a5a..0cd254121ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -16,22 +16,13 @@ package org.springframework.boot.autoconfigure.web; -import java.io.File; import java.net.InetAddress; -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.HashMap; -import java.util.LinkedHashMap; -import java.util.List; 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.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.convert.DurationUnit; import org.springframework.boot.web.server.Compression; @@ -133,14 +124,6 @@ public class ServerProperties { private final Reactive reactive = new Reactive(); - private final Tomcat tomcat = new Tomcat(); - - private final Jetty jetty = new Jetty(); - - private final Netty netty = new Netty(); - - private final Undertow undertow = new Undertow(); - public Integer getPort() { return this.port; } @@ -217,22 +200,6 @@ public class ServerProperties { return this.reactive; } - public Tomcat getTomcat() { - return this.tomcat; - } - - public Jetty getJetty() { - return this.jetty; - } - - public Netty getNetty() { - return this.netty; - } - - public Undertow getUndertow() { - return this.undertow; - } - public ForwardHeadersStrategy getForwardHeadersStrategy() { return this.forwardHeadersStrategy; } @@ -380,1574 +347,6 @@ public class ServerProperties { } - /** - * Tomcat properties. - */ - public static class Tomcat { - - /** - * Access log configuration. - */ - private final Accesslog accesslog = new Accesslog(); - - /** - * Thread related configuration. - */ - private final Threads threads = new Threads(); - - /** - * 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 additionalTldSkipPatterns = new ArrayList<>(); - - /** - * List of additional unencoded characters that should be allowed in URI paths. - * Only "< > [ \ ] ^ ` { | }" are allowed. - */ - private List relaxedPathChars = new ArrayList<>(); - - /** - * List of additional unencoded characters that should be allowed in URI query - * strings. Only "< > [ \ ] ^ ` { | }" are allowed. - */ - private List relaxedQueryChars = new ArrayList<>(); - - /** - * Amount of time the connector will wait, after accepting a connection, for the - * request URI line to be presented. - */ - private Duration connectionTimeout; - - /** - * Static resource configuration. - */ - 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(); - - /** - * 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; - - public Accesslog getAccesslog() { - return this.accesslog; - } - - public Threads getThreads() { - return this.threads; - } - - 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 getAdditionalTldSkipPatterns() { - return this.additionalTldSkipPatterns; - } - - public void setAdditionalTldSkipPatterns(List additionalTldSkipPatterns) { - this.additionalTldSkipPatterns = additionalTldSkipPatterns; - } - - public List getRelaxedPathChars() { - return this.relaxedPathChars; - } - - public void setRelaxedPathChars(List relaxedPathChars) { - this.relaxedPathChars = relaxedPathChars; - } - - public List getRelaxedQueryChars() { - return this.relaxedQueryChars; - } - - public void setRelaxedQueryChars(List relaxedQueryChars) { - this.relaxedQueryChars = relaxedQueryChars; - } - - public Duration getConnectionTimeout() { - return this.connectionTimeout; - } - - public void setConnectionTimeout(Duration connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - public Resource getResource() { - return this.resource; - } - - public Mbeanregistry getMbeanregistry() { - return this.mbeanregistry; - } - - public Remoteip getRemoteip() { - return this.remoteip; - } - - 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; - } - - /** - * 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 - - } - - } - - /** - * Jetty properties. - */ - public static class Jetty { - - /** - * Access log configuration. - */ - private final Accesslog accesslog = new Accesslog(); - - /** - * Thread related configuration. - */ - private final Threads threads = new Threads(); - - /** - * 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; - - 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 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 ignorePaths; - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public FORMAT getFormat() { - return this.format; - } - - public void setFormat(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 getIgnorePaths() { - return this.ignorePaths; - } - - public void setIgnorePaths(List 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; - } - - } - - } - - /** - * Netty properties. - */ - public static class Netty { - - /** - * 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; - } - - } - - /** - * Undertow properties. - */ - public static class Undertow { - - /** - * 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 Accesslog getAccesslog() { - return this.accesslog; - } - - public Threads getThreads() { - return this.threads; - } - - public 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 socket = new LinkedHashMap<>(); - - /** - * Server options as defined in io.undertow.UndertowOptions. - */ - private final Map server = new LinkedHashMap<>(); - - public Map getServer() { - return this.server; - } - - public Map getSocket() { - return this.socket; - } - - } - - } - /** * Strategies for supporting forward headers. */ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyServerProperties.java new file mode 100644 index 00000000000..28bac2c35aa --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyServerProperties.java @@ -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 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 getIgnorePaths() { + return this.ignorePaths; + } + + public void setIgnorePaths(List 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; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyThreadPool.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyThreadPool.java index ccf72f6be2c..78f6e543adf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyThreadPool.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyThreadPool.java @@ -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 queue = determineBlockingQueue(properties.getMaxQueueCapacity()); int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200; int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizer.java index 78a8f66e2a9..55eb6c718f4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizer.java @@ -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, Ordered { + implements WebServerFactoryCustomizer, 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)); + } + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerConfiguration.java index 2ab7f92da42..d4f3e506d1e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerConfiguration.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizer.java index 4531b5e696d..3688af2a224 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizer.java @@ -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; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/jetty/JettyReactiveWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/jetty/JettyReactiveWebServerAutoConfiguration.java index 0ddf9114d8e..4c5d9df72ba 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/jetty/JettyReactiveWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/jetty/JettyReactiveWebServerAutoConfiguration.java @@ -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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerAutoConfiguration.java index dcf3889b5a2..9a69ed2f243 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerAutoConfiguration.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizer.java index e6d92de7bed..8ce73574672 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizer.java @@ -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; })); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyServerProperties.java new file mode 100644 index 00000000000..6687b76bfe7 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyServerProperties.java @@ -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; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerAutoConfiguration.java index 2a509b186ab..7a4dfbfa51e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerAutoConfiguration.java @@ -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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerFactoryCustomizer.java index 98604ec9dfb..d60397873c7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/tomcat/TomcatReactiveWebServerFactoryCustomizer.java @@ -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 { - 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()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/undertow/UndertowReactiveWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/undertow/UndertowReactiveWebServerAutoConfiguration.java index 3404a1a93be..35eb5564be3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/undertow/UndertowReactiveWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/reactive/undertow/UndertowReactiveWebServerAutoConfiguration.java @@ -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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/jetty/JettyServletWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/jetty/JettyServletWebServerAutoConfiguration.java index 81e4a1437cc..f002545f98c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/jetty/JettyServletWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/jetty/JettyServletWebServerAutoConfiguration.java @@ -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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerAutoConfiguration.java index da3c212d14f..5fe2529cef5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerAutoConfiguration.java @@ -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()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizer.java index 3d5650d12cb..12efedbdd0f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizer.java @@ -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, 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) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerAutoConfiguration.java index 8e78e4176f6..4465d1ee40c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerAutoConfiguration.java @@ -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) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizer.java index 2f2c4a0ce98..f5d88927b50 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizer.java @@ -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 { - 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()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatServerProperties.java new file mode 100644 index 00000000000..06ab747565e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatServerProperties.java @@ -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 additionalTldSkipPatterns = new ArrayList<>(); + + /** + * List of additional unencoded characters that should be allowed in URI paths. Only + * "< > [ \ ] ^ ` { | }" are allowed. + */ + private List relaxedPathChars = new ArrayList<>(); + + /** + * List of additional unencoded characters that should be allowed in URI query + * strings. Only "< > [ \ ] ^ ` { | }" are allowed. + */ + private List relaxedQueryChars = new ArrayList<>(); + + /** + * Amount of time the connector will wait, after accepting a connection, for the + * request URI line to be presented. + */ + private Duration connectionTimeout; + + /** + * 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 getAdditionalTldSkipPatterns() { + return this.additionalTldSkipPatterns; + } + + public void setAdditionalTldSkipPatterns(List additionalTldSkipPatterns) { + this.additionalTldSkipPatterns = additionalTldSkipPatterns; + } + + public List getRelaxedPathChars() { + return this.relaxedPathChars; + } + + public void setRelaxedPathChars(List relaxedPathChars) { + this.relaxedPathChars = relaxedPathChars; + } + + public List getRelaxedQueryChars() { + return this.relaxedQueryChars; + } + + public void setRelaxedQueryChars(List 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 + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerConfiguration.java index 50098f05f8d..8d36168592c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerConfiguration.java @@ -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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizer.java index 6bb5ffbde7e..2e621309dbd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizer.java @@ -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()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowServerProperties.java new file mode 100644 index 00000000000..17c35266ea4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowServerProperties.java @@ -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 socket = new LinkedHashMap<>(); + + /** + * Server options as defined in io.undertow.UndertowOptions. + */ + private final Map server = new LinkedHashMap<>(); + + public Map getServer() { + return this.server; + } + + public Map getSocket() { + return this.socket; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerConfiguration.java index a78bbb14d92..054207d2864 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerConfiguration.java @@ -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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizer.java index 716ef1b7ddf..4cd77a9ee1c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizer.java @@ -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); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/JettyServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/JettyServerPropertiesTests.java new file mode 100644 index 00000000000..e2de0600f65 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/JettyServerPropertiesTests.java @@ -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 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 map) { + ConfigurationPropertySource source = new MapConfigurationPropertySource(map); + new Binder(source).bind("server.jetty", Bindable.ofInstance(this.properties)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/NettyServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/NettyServerPropertiesTests.java new file mode 100644 index 00000000000..dfa16bfd19e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/NettyServerPropertiesTests.java @@ -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 map) { + ConfigurationPropertySource source = new MapConfigurationPropertySource(map); + new Binder(source).bind("server.netty", Bindable.ofInstance(this.properties)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index 98eff18d778..543167aa35e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -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 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 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)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/TomcatServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/TomcatServerPropertiesTests.java new file mode 100644 index 00000000000..d4fa82b9cf3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/TomcatServerPropertiesTests.java @@ -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 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 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(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/UndertowServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/UndertowServerPropertiesTests.java new file mode 100644 index 00000000000..7430ea38a48 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/UndertowServerPropertiesTests.java @@ -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 map) { + ConfigurationPropertySource source = new MapConfigurationPropertySource(map); + new Binder(source).bind("server.undertow", Bindable.ofInstance(this.properties)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizerTests.java index 21ab20503e9..b447426c929 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyVirtualThreadsWebServerFactoryCustomizerTests.java @@ -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); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizerTests.java index 5ed056e9709..29c2c733a09 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/jetty/JettyWebServerFactoryCustomizerTests.java @@ -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() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizerTests.java index 9caee399e3b..4c24e990452 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/reactive/netty/NettyReactiveWebServerFactoryCustomizerTests.java @@ -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)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/ServletWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/ServletWebServerFactoryCustomizerTests.java index 4e45a80e21f..99ba6c56c33 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/ServletWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/ServletWebServerFactoryCustomizerTests.java @@ -164,14 +164,6 @@ class ServletWebServerFactoryCustomizerTests { then(factory).should().setDisplayName("MyBootApp"); } - @Test - void testCustomizeTomcatMinSpareThreads() { - Map 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 map = new HashMap<>(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizerTests.java index 5fe76d994a2..175d0ec31fb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/tomcat/TomcatServletWebServerFactoryCustomizerTests.java @@ -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() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizerTests.java index c68a6dd0e2f..e9aa85a4ea2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/servlet/undertow/UndertowServletWebServerFactoryCustomizerTests.java @@ -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(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizerTests.java index 8297ab3ba0b..735e40a76fb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/tomcat/TomcatWebServerFactoryCustomizerTests.java @@ -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 consumer) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizerTests.java index 74896ff2a1f..0a7cf85c503 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/server/undertow/UndertowWebServerFactoryCustomizerTests.java @@ -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)); } }