diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java index d3136ccb60..accc1517b9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java @@ -22,7 +22,7 @@ import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerFactoryCustomizer; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.web.reactive.DefaultReactiveWebServerCustomizer; +import org.springframework.boot.autoconfigure.web.reactive.DefaultReactiveWebServerFactoryCustomizer; import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -58,7 +58,7 @@ public class ReactiveManagementChildContextConfiguration { ManagementServerFactoryCustomizer { ReactiveManagementServerFactoryCustomizer(ListableBeanFactory beanFactory) { - super(beanFactory, DefaultReactiveWebServerCustomizer.class); + super(beanFactory, DefaultReactiveWebServerFactoryCustomizer.class); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/TomcatCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/TomcatCustomizer.java new file mode 100644 index 0000000000..e829752508 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/TomcatCustomizer.java @@ -0,0 +1,230 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.embedded.tomcat; + +import java.time.Duration; + +import org.apache.catalina.Lifecycle; +import org.apache.catalina.valves.AccessLogValve; +import org.apache.catalina.valves.RemoteIpValve; +import org.apache.coyote.AbstractProtocol; +import org.apache.coyote.ProtocolHandler; +import org.apache.coyote.http11.AbstractHttp11Protocol; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +/** + * Customization for Tomcat-specific features common + * for both Servlet and Reactive servers. + * + * @author Brian Clozel + * @since 2.0.0 + */ +public final class TomcatCustomizer { + + private TomcatCustomizer() { + } + + public static void customizeTomcat(ServerProperties serverProperties, + Environment environment, ConfigurableTomcatWebServerFactory factory) { + ServerProperties.Tomcat tomcatProperties = serverProperties.getTomcat(); + if (tomcatProperties.getBasedir() != null) { + factory.setBaseDirectory(tomcatProperties.getBasedir()); + } + if (tomcatProperties.getBackgroundProcessorDelay() != null) { + factory.setBackgroundProcessorDelay((int) tomcatProperties + .getBackgroundProcessorDelay().getSeconds()); + } + customizeRemoteIpValve(serverProperties, environment, factory); + if (tomcatProperties.getMaxThreads() > 0) { + customizeMaxThreads(factory, tomcatProperties.getMaxThreads()); + } + if (tomcatProperties.getMinSpareThreads() > 0) { + customizeMinThreads(factory, tomcatProperties.getMinSpareThreads()); + } + int maxHttpHeaderSize = (serverProperties.getMaxHttpHeaderSize() > 0 + ? serverProperties.getMaxHttpHeaderSize() + : tomcatProperties.getMaxHttpHeaderSize()); + if (maxHttpHeaderSize > 0) { + customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize); + } + if (tomcatProperties.getMaxHttpPostSize() != 0) { + customizeMaxHttpPostSize(factory, tomcatProperties.getMaxHttpPostSize()); + } + if (tomcatProperties.getAccesslog().isEnabled()) { + customizeAccessLog(tomcatProperties, factory); + } + if (tomcatProperties.getUriEncoding() != null) { + factory.setUriEncoding(tomcatProperties.getUriEncoding()); + } + if (serverProperties.getConnectionTimeout() != null) { + customizeConnectionTimeout(factory, + serverProperties.getConnectionTimeout()); + } + if (tomcatProperties.getMaxConnections() > 0) { + customizeMaxConnections(factory, tomcatProperties.getMaxConnections()); + } + if (tomcatProperties.getAcceptCount() > 0) { + customizeAcceptCount(factory, tomcatProperties.getAcceptCount()); + } + customizeStaticResources(serverProperties.getTomcat().getResource(), factory); + } + + private static void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, + int acceptCount) { + factory.addConnectorCustomizers((connector) -> { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractProtocol) { + AbstractProtocol protocol = (AbstractProtocol) handler; + protocol.setAcceptCount(acceptCount); + } + }); + } + + private static void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory, + int maxConnections) { + factory.addConnectorCustomizers((connector) -> { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractProtocol) { + AbstractProtocol protocol = (AbstractProtocol) handler; + protocol.setMaxConnections(maxConnections); + } + }); + } + + private static void customizeConnectionTimeout( + ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) { + factory.addConnectorCustomizers((connector) -> { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractProtocol) { + AbstractProtocol protocol = (AbstractProtocol) handler; + protocol.setConnectionTimeout((int) connectionTimeout.toMillis()); + } + }); + } + + private static void customizeRemoteIpValve(ServerProperties properties, + Environment environment, ConfigurableTomcatWebServerFactory factory) { + String protocolHeader = properties.getTomcat().getProtocolHeader(); + String remoteIpHeader = properties.getTomcat().getRemoteIpHeader(); + // For back compatibility the valve is also enabled if protocol-header is set + if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader) + || getOrDeduceUseForwardHeaders(properties, environment)) { + RemoteIpValve valve = new RemoteIpValve(); + valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) + ? protocolHeader : "X-Forwarded-Proto"); + if (StringUtils.hasLength(remoteIpHeader)) { + valve.setRemoteIpHeader(remoteIpHeader); + } + // The internal proxies default to a white list of "safe" internal IP + // addresses + valve.setInternalProxies(properties.getTomcat().getInternalProxies()); + valve.setPortHeader(properties.getTomcat().getPortHeader()); + valve.setProtocolHeaderHttpsValue( + properties.getTomcat().getProtocolHeaderHttpsValue()); + // ... so it's safe to add this valve by default. + factory.addEngineValves(valve); + } + } + + private static boolean getOrDeduceUseForwardHeaders(ServerProperties serverProperties, + Environment environment) { + if (serverProperties.isUseForwardHeaders() != null) { + return serverProperties.isUseForwardHeaders(); + } + CloudPlatform platform = CloudPlatform.getActive(environment); + return platform != null && platform.isUsingForwardHeaders(); + } + + @SuppressWarnings("rawtypes") + private static void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, + int maxThreads) { + factory.addConnectorCustomizers((connector) -> { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractProtocol) { + AbstractProtocol protocol = (AbstractProtocol) handler; + protocol.setMaxThreads(maxThreads); + } + }); + } + + @SuppressWarnings("rawtypes") + private static void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, + int minSpareThreads) { + factory.addConnectorCustomizers((connector) -> { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractProtocol) { + AbstractProtocol protocol = (AbstractProtocol) handler; + protocol.setMinSpareThreads(minSpareThreads); + } + }); + } + + @SuppressWarnings("rawtypes") + private static void customizeMaxHttpHeaderSize( + ConfigurableTomcatWebServerFactory factory, int maxHttpHeaderSize) { + factory.addConnectorCustomizers((connector) -> { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractHttp11Protocol) { + AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; + protocol.setMaxHttpHeaderSize(maxHttpHeaderSize); + } + }); + } + + private static void customizeMaxHttpPostSize( + ConfigurableTomcatWebServerFactory factory, int maxHttpPostSize) { + factory.addConnectorCustomizers( + (connector) -> connector.setMaxPostSize(maxHttpPostSize)); + } + + private static void customizeAccessLog(ServerProperties.Tomcat tomcatProperties, + ConfigurableTomcatWebServerFactory factory) { + + AccessLogValve valve = new AccessLogValve(); + valve.setPattern(tomcatProperties.getAccesslog().getPattern()); + valve.setDirectory(tomcatProperties.getAccesslog().getDirectory()); + valve.setPrefix(tomcatProperties.getAccesslog().getPrefix()); + valve.setSuffix(tomcatProperties.getAccesslog().getSuffix()); + valve.setRenameOnRotate(tomcatProperties.getAccesslog().isRenameOnRotate()); + valve.setFileDateFormat(tomcatProperties.getAccesslog().getFileDateFormat()); + valve.setRequestAttributesEnabled( + tomcatProperties.getAccesslog().isRequestAttributesEnabled()); + valve.setRotatable(tomcatProperties.getAccesslog().isRotate()); + valve.setBuffered(tomcatProperties.getAccesslog().isBuffered()); + factory.addEngineValves(valve); + } + + private static void customizeStaticResources(ServerProperties.Tomcat.Resource resource, + ConfigurableTomcatWebServerFactory factory) { + if (resource.getCacheTtl() == null) { + return; + } + factory.addContextCustomizers((context) -> { + context.addLifecycleListener((event) -> { + if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { + long ttl = resource.getCacheTtl().toMillis(); + context.getResources().setCacheTtl(ttl); + } + }); + }); + } +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/package-info.java new file mode 100644 index 0000000000..c20886aee7 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012-2018 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 + * + * http://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. + */ + +/** + * Configuration for embedded reactive and servlet Tomcat web servers. + * + * @see org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory + */ +package org.springframework.boot.autoconfigure.web.embedded.tomcat; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizer.java similarity index 56% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerCustomizer.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizer.java index db98b05652..34810f85ac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -17,9 +17,13 @@ package org.springframework.boot.autoconfigure.web.reactive; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.embedded.tomcat.TomcatCustomizer; +import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.EnvironmentAware; import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; /** * Default {@link WebServerFactoryCustomizer} for reactive servers. @@ -27,12 +31,14 @@ import org.springframework.core.Ordered; * @author Brian Clozel * @since 2.0.0 */ -public class DefaultReactiveWebServerCustomizer implements - WebServerFactoryCustomizer, Ordered { +public class DefaultReactiveWebServerFactoryCustomizer implements + WebServerFactoryCustomizer, EnvironmentAware, Ordered { private final ServerProperties serverProperties; - public DefaultReactiveWebServerCustomizer(ServerProperties serverProperties) { + private Environment environment; + + public DefaultReactiveWebServerFactoryCustomizer(ServerProperties serverProperties) { this.serverProperties = serverProperties; } @@ -42,21 +48,30 @@ public class DefaultReactiveWebServerCustomizer implements } @Override - public void customize(ConfigurableReactiveWebServerFactory server) { + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Override + public void customize(ConfigurableReactiveWebServerFactory factory) { if (this.serverProperties.getPort() != null) { - server.setPort(this.serverProperties.getPort()); + factory.setPort(this.serverProperties.getPort()); } if (this.serverProperties.getAddress() != null) { - server.setAddress(this.serverProperties.getAddress()); + factory.setAddress(this.serverProperties.getAddress()); } if (this.serverProperties.getSsl() != null) { - server.setSsl(this.serverProperties.getSsl()); + factory.setSsl(this.serverProperties.getSsl()); } if (this.serverProperties.getCompression() != null) { - server.setCompression(this.serverProperties.getCompression()); + factory.setCompression(this.serverProperties.getCompression()); } if (this.serverProperties.getHttp2() != null) { - server.setHttp2(this.serverProperties.getHttp2()); + factory.setHttp2(this.serverProperties.getHttp2()); + } + if (factory instanceof TomcatReactiveWebServerFactory) { + TomcatCustomizer.customizeTomcat(this.serverProperties, this.environment, + (TomcatReactiveWebServerFactory) factory); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfiguration.java index 0625988738..574123a291 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfiguration.java @@ -59,9 +59,9 @@ public class ReactiveWebServerAutoConfiguration { @ConditionalOnMissingBean @Bean - public DefaultReactiveWebServerCustomizer defaultReactiveWebServerCustomizer( + public DefaultReactiveWebServerFactoryCustomizer defaultReactiveWebServerCustomizer( ServerProperties serverProperties) { - return new DefaultReactiveWebServerCustomizer(serverProperties); + return new DefaultReactiveWebServerFactoryCustomizer(serverProperties); } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java index 78d0752183..64caaf30fa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java @@ -25,12 +25,6 @@ import javax.servlet.ServletException; import javax.servlet.SessionCookieConfig; import io.undertow.UndertowOptions; -import org.apache.catalina.Lifecycle; -import org.apache.catalina.valves.AccessLogValve; -import org.apache.catalina.valves.RemoteIpValve; -import org.apache.coyote.AbstractProtocol; -import org.apache.coyote.ProtocolHandler; -import org.apache.coyote.http11.AbstractHttp11Protocol; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Handler; @@ -43,10 +37,11 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties.Session; -import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Resource; +import org.springframework.boot.autoconfigure.web.embedded.tomcat.TomcatCustomizer; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; @@ -57,7 +52,6 @@ import org.springframework.context.EnvironmentAware; import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; /** * Default {@link WebServerFactoryCustomizer} for {@link ServerProperties}. @@ -126,8 +120,9 @@ public class DefaultServletWebServerFactoryCustomizer } factory.setServerHeader(this.serverProperties.getServerHeader()); if (factory instanceof TomcatServletWebServerFactory) { - TomcatCustomizer.customizeTomcat(this.serverProperties, this.environment, - (TomcatServletWebServerFactory) factory); + TomcatServletWebServerFactory tomcatFactory = (TomcatServletWebServerFactory) factory; + TomcatCustomizer.customizeTomcat(this.serverProperties, this.environment, tomcatFactory); + TomcatServletCustomizer.customizeTomcat(this.serverProperties, this.environment, tomcatFactory); } if (factory instanceof JettyServletWebServerFactory) { JettyCustomizer.customizeJetty(this.serverProperties, this.environment, @@ -150,7 +145,7 @@ public class DefaultServletWebServerFactoryCustomizer return serverProperties.isUseForwardHeaders(); } CloudPlatform platform = CloudPlatform.getActive(environment); - return (platform == null ? false : platform.isUsingForwardHeaders()); + return platform != null && platform.isUsingForwardHeaders(); } /** @@ -214,43 +209,14 @@ public class DefaultServletWebServerFactoryCustomizer } - private static class TomcatCustomizer { + private static class TomcatServletCustomizer { public static void customizeTomcat(ServerProperties serverProperties, Environment environment, TomcatServletWebServerFactory factory) { ServerProperties.Tomcat tomcatProperties = serverProperties.getTomcat(); - if (tomcatProperties.getBasedir() != null) { - factory.setBaseDirectory(tomcatProperties.getBasedir()); - } - if (tomcatProperties.getBackgroundProcessorDelay() != null) { - factory.setBackgroundProcessorDelay((int) tomcatProperties - .getBackgroundProcessorDelay().getSeconds()); - } - customizeRemoteIpValve(serverProperties, environment, factory); - if (tomcatProperties.getMaxThreads() > 0) { - customizeMaxThreads(factory, tomcatProperties.getMaxThreads()); - } - if (tomcatProperties.getMinSpareThreads() > 0) { - customizeMinThreads(factory, tomcatProperties.getMinSpareThreads()); - } - int maxHttpHeaderSize = (serverProperties.getMaxHttpHeaderSize() > 0 - ? serverProperties.getMaxHttpHeaderSize() - : tomcatProperties.getMaxHttpHeaderSize()); - if (maxHttpHeaderSize > 0) { - customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize); - } - if (tomcatProperties.getMaxHttpPostSize() != 0) { - customizeMaxHttpPostSize(factory, tomcatProperties.getMaxHttpPostSize()); - } - if (tomcatProperties.getAccesslog().isEnabled()) { - customizeAccessLog(tomcatProperties, factory); - } - if (tomcatProperties.getUriEncoding() != null) { - factory.setUriEncoding(tomcatProperties.getUriEncoding()); - } - if (serverProperties.getConnectionTimeout() != null) { - customizeConnectionTimeout(factory, - serverProperties.getConnectionTimeout()); + if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { + factory.getTldSkipPatterns() + .addAll(tomcatProperties.getAdditionalTldSkipPatterns()); } if (tomcatProperties.getRedirectContextRoot() != null) { customizeRedirectContextRoot(factory, @@ -260,162 +226,19 @@ public class DefaultServletWebServerFactoryCustomizer customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects()); } - if (tomcatProperties.getMaxConnections() > 0) { - customizeMaxConnections(factory, tomcatProperties.getMaxConnections()); - } - if (tomcatProperties.getAcceptCount() > 0) { - customizeAcceptCount(factory, tomcatProperties.getAcceptCount()); - } - if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { - factory.getTldSkipPatterns() - .addAll(tomcatProperties.getAdditionalTldSkipPatterns()); - } - customizeStaticResources(serverProperties.getTomcat().getResource(), factory); - } - - private static void customizeAcceptCount(TomcatServletWebServerFactory factory, - int acceptCount) { - factory.addConnectorCustomizers((connector) -> { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractProtocol) { - AbstractProtocol protocol = (AbstractProtocol) handler; - protocol.setAcceptCount(acceptCount); - } - }); - } - - private static void customizeMaxConnections(TomcatServletWebServerFactory factory, - int maxConnections) { - factory.addConnectorCustomizers((connector) -> { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractProtocol) { - AbstractProtocol protocol = (AbstractProtocol) handler; - protocol.setMaxConnections(maxConnections); - } - }); - } - - private static void customizeConnectionTimeout( - TomcatServletWebServerFactory factory, Duration connectionTimeout) { - factory.addConnectorCustomizers((connector) -> { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractProtocol) { - AbstractProtocol protocol = (AbstractProtocol) handler; - protocol.setConnectionTimeout((int) connectionTimeout.toMillis()); - } - }); - } - - private static void customizeRemoteIpValve(ServerProperties properties, - Environment environment, TomcatServletWebServerFactory factory) { - String protocolHeader = properties.getTomcat().getProtocolHeader(); - String remoteIpHeader = properties.getTomcat().getRemoteIpHeader(); - // For back compatibility the valve is also enabled if protocol-header is set - if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader) - || getOrDeduceUseForwardHeaders(properties, environment)) { - RemoteIpValve valve = new RemoteIpValve(); - valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) - ? protocolHeader : "X-Forwarded-Proto"); - if (StringUtils.hasLength(remoteIpHeader)) { - valve.setRemoteIpHeader(remoteIpHeader); - } - // The internal proxies default to a white list of "safe" internal IP - // addresses - valve.setInternalProxies(properties.getTomcat().getInternalProxies()); - valve.setPortHeader(properties.getTomcat().getPortHeader()); - valve.setProtocolHeaderHttpsValue( - properties.getTomcat().getProtocolHeaderHttpsValue()); - // ... so it's safe to add this valve by default. - factory.addEngineValves(valve); - } - } - - @SuppressWarnings("rawtypes") - private static void customizeMaxThreads(TomcatServletWebServerFactory factory, - int maxThreads) { - factory.addConnectorCustomizers((connector) -> { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractProtocol) { - AbstractProtocol protocol = (AbstractProtocol) handler; - protocol.setMaxThreads(maxThreads); - } - }); - } - - @SuppressWarnings("rawtypes") - private static void customizeMinThreads(TomcatServletWebServerFactory factory, - int minSpareThreads) { - factory.addConnectorCustomizers((connector) -> { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractProtocol) { - AbstractProtocol protocol = (AbstractProtocol) handler; - protocol.setMinSpareThreads(minSpareThreads); - } - }); - } - - @SuppressWarnings("rawtypes") - private static void customizeMaxHttpHeaderSize( - TomcatServletWebServerFactory factory, int maxHttpHeaderSize) { - factory.addConnectorCustomizers((connector) -> { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractHttp11Protocol) { - AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; - protocol.setMaxHttpHeaderSize(maxHttpHeaderSize); - } - }); - } - - private static void customizeMaxHttpPostSize( - TomcatServletWebServerFactory factory, int maxHttpPostSize) { - factory.addConnectorCustomizers( - (connector) -> connector.setMaxPostSize(maxHttpPostSize)); - } - - private static void customizeAccessLog(ServerProperties.Tomcat tomcatProperties, - TomcatServletWebServerFactory factory) { - - AccessLogValve valve = new AccessLogValve(); - valve.setPattern(tomcatProperties.getAccesslog().getPattern()); - valve.setDirectory(tomcatProperties.getAccesslog().getDirectory()); - valve.setPrefix(tomcatProperties.getAccesslog().getPrefix()); - valve.setSuffix(tomcatProperties.getAccesslog().getSuffix()); - valve.setRenameOnRotate(tomcatProperties.getAccesslog().isRenameOnRotate()); - valve.setFileDateFormat(tomcatProperties.getAccesslog().getFileDateFormat()); - valve.setRequestAttributesEnabled( - tomcatProperties.getAccesslog().isRequestAttributesEnabled()); - valve.setRotatable(tomcatProperties.getAccesslog().isRotate()); - valve.setBuffered(tomcatProperties.getAccesslog().isBuffered()); - factory.addEngineValves(valve); } private static void customizeRedirectContextRoot( - TomcatServletWebServerFactory factory, boolean redirectContextRoot) { + ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) { factory.addContextCustomizers((context) -> context .setMapperContextRootRedirectEnabled(redirectContextRoot)); } private static void customizeUseRelativeRedirects( - TomcatServletWebServerFactory factory, boolean useRelativeRedirects) { + ConfigurableTomcatWebServerFactory factory, boolean useRelativeRedirects) { factory.addContextCustomizers( (context) -> context.setUseRelativeRedirects(useRelativeRedirects)); } - - private static void customizeStaticResources(Resource resource, - TomcatServletWebServerFactory factory) { - if (resource.getCacheTtl() == null) { - return; - } - factory.addContextCustomizers((context) -> { - context.addLifecycleListener((event) -> { - if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { - long ttl = resource.getCacheTtl().toMillis(); - context.getResources().setCacheTtl(ttl); - } - }); - }); - } - } private static class UndertowCustomizer { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerCustomizerTests.java deleted file mode 100644 index ebdb02e239..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerCustomizerTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-2017 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 - * - * http://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.reactive; - -import java.net.InetAddress; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link DefaultReactiveWebServerCustomizer}. - * - * @author Brian Clozel - */ -public class DefaultReactiveWebServerCustomizerTests { - - private final ServerProperties properties = new ServerProperties(); - - private DefaultReactiveWebServerCustomizer customizer; - - @Before - public void setup() { - this.customizer = new DefaultReactiveWebServerCustomizer(this.properties); - } - - @Test - public void testCustomizeServerPort() { - ConfigurableReactiveWebServerFactory factory = mock( - ConfigurableReactiveWebServerFactory.class); - this.properties.setPort(9000); - this.customizer.customize(factory); - verify(factory).setPort(9000); - } - - @Test - public void testCustomizeServerAddress() { - ConfigurableReactiveWebServerFactory factory = mock( - ConfigurableReactiveWebServerFactory.class); - InetAddress address = mock(InetAddress.class); - this.properties.setAddress(address); - this.customizer.customize(factory); - verify(factory).setAddress(address); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java new file mode 100644 index 0000000000..a3d158ae09 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java @@ -0,0 +1,348 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.reactive; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; + +import org.apache.catalina.Context; +import org.apache.catalina.Valve; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.valves.AccessLogValve; +import org.apache.catalina.valves.RemoteIpValve; +import org.apache.coyote.AbstractProtocol; +import org.junit.Before; +import org.junit.Test; + +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.context.properties.source.ConfigurationPropertySource; +import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; +import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; +import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link DefaultReactiveWebServerFactoryCustomizer}. + * + * @author Brian Clozel + */ +public class DefaultReactiveWebServerFactoryCustomizerTests { + + private final ServerProperties properties = new ServerProperties(); + + private DefaultReactiveWebServerFactoryCustomizer customizer; + + @Before + public void setup() { + this.customizer = new DefaultReactiveWebServerFactoryCustomizer(this.properties); + } + + @Test + public void testCustomizeServerPort() { + ConfigurableReactiveWebServerFactory factory = mock( + ConfigurableReactiveWebServerFactory.class); + this.properties.setPort(9000); + this.customizer.customize(factory); + verify(factory).setPort(9000); + } + + @Test + public void testCustomizeServerAddress() { + ConfigurableReactiveWebServerFactory factory = mock( + ConfigurableReactiveWebServerFactory.class); + InetAddress address = mock(InetAddress.class); + this.properties.setAddress(address); + this.customizer.customize(factory); + verify(factory).setAddress(address); + } + + @Test + public void tomcatAccessLogIsDisabledByDefault() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + this.customizer.customize(factory); + assertThat(factory.getEngineValves()).isEmpty(); + } + + @Test + public void tomcatAccessLogCanBeEnabled() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + Map map = new HashMap<>(); + map.put("server.tomcat.accesslog.enabled", "true"); + bindProperties(map); + this.customizer.customize(factory); + assertThat(factory.getEngineValves()).hasSize(1); + assertThat(factory.getEngineValves()).first().isInstanceOf(AccessLogValve.class); + } + + @Test + public void tomcatAccessLogFileDateFormatByDefault() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + Map map = new HashMap<>(); + map.put("server.tomcat.accesslog.enabled", "true"); + bindProperties(map); + this.customizer.customize(factory); + assertThat(((AccessLogValve) factory.getEngineValves().iterator().next()) + .getFileDateFormat()).isEqualTo(".yyyy-MM-dd"); + } + + @Test + public void tomcatAccessLogFileDateFormatCanBeRedefined() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + Map map = new HashMap<>(); + map.put("server.tomcat.accesslog.enabled", "true"); + map.put("server.tomcat.accesslog.file-date-format", "yyyy-MM-dd.HH"); + bindProperties(map); + this.customizer.customize(factory); + assertThat(((AccessLogValve) factory.getEngineValves().iterator().next()) + .getFileDateFormat()).isEqualTo("yyyy-MM-dd.HH"); + } + + @Test + public void tomcatAccessLogIsBufferedByDefault() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + Map map = new HashMap<>(); + map.put("server.tomcat.accesslog.enabled", "true"); + bindProperties(map); + this.customizer.customize(factory); + assertThat(((AccessLogValve) factory.getEngineValves().iterator().next()) + .isBuffered()).isTrue(); + } + + @Test + public void tomcatAccessLogBufferingCanBeDisabled() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + Map map = new HashMap<>(); + map.put("server.tomcat.accesslog.enabled", "true"); + map.put("server.tomcat.accesslog.buffered", "false"); + bindProperties(map); + this.customizer.customize(factory); + assertThat(((AccessLogValve) factory.getEngineValves().iterator().next()) + .isBuffered()).isFalse(); + } + + @Test + public void disableTomcatRemoteIpValve() { + Map map = new HashMap<>(); + map.put("server.tomcat.remote-ip-header", ""); + map.put("server.tomcat.protocol-header", ""); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + this.customizer.customize(factory); + assertThat(factory.getEngineValves()).isEmpty(); + } + + @Test + public void defaultTomcatBackgroundProcessorDelay() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + this.customizer.customize(factory); + TomcatWebServer webServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class)); + assertThat(webServer.getTomcat().getEngine().getBackgroundProcessorDelay()) + .isEqualTo(30); + webServer.stop(); + } + + @Test + public void customTomcatBackgroundProcessorDelay() { + Map map = new HashMap<>(); + map.put("server.tomcat.background-processor-delay", "5"); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + this.customizer.customize(factory); + TomcatWebServer webServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class)); + assertThat(webServer.getTomcat().getEngine().getBackgroundProcessorDelay()) + .isEqualTo(5); + webServer.stop(); + } + + @Test + public void defaultTomcatRemoteIpValve() { + Map map = new HashMap<>(); + // Since 1.1.7 you need to specify at least the protocol + map.put("server.tomcat.protocol-header", "X-Forwarded-Proto"); + map.put("server.tomcat.remote-ip-header", "X-Forwarded-For"); + bindProperties(map); + testRemoteIpValveConfigured(); + } + + @Test + public void setUseForwardHeadersTomcat() { + // Since 1.3.0 no need to explicitly set header names if use-forward-header=true + this.properties.setUseForwardHeaders(true); + testRemoteIpValveConfigured(); + } + + @Test + public void deduceUseForwardHeadersTomcat() { + this.customizer.setEnvironment(new MockEnvironment().withProperty("DYNO", "-")); + testRemoteIpValveConfigured(); + } + + private void testRemoteIpValveConfigured() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + this.customizer.customize(factory); + assertThat(factory.getEngineValves()).hasSize(1); + Valve valve = factory.getEngineValves().iterator().next(); + assertThat(valve).isInstanceOf(RemoteIpValve.class); + RemoteIpValve remoteIpValve = (RemoteIpValve) valve; + assertThat(remoteIpValve.getProtocolHeader()).isEqualTo("X-Forwarded-Proto"); + assertThat(remoteIpValve.getProtocolHeaderHttpsValue()).isEqualTo("https"); + assertThat(remoteIpValve.getRemoteIpHeader()).isEqualTo("X-Forwarded-For"); + String expectedInternalProxies = "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 + + "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\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}"; + assertThat(remoteIpValve.getInternalProxies()).isEqualTo(expectedInternalProxies); + } + + @Test + public void customTomcatRemoteIpValve() { + Map map = new HashMap<>(); + map.put("server.tomcat.remote-ip-header", "x-my-remote-ip-header"); + map.put("server.tomcat.protocol-header", "x-my-protocol-header"); + map.put("server.tomcat.internal-proxies", "192.168.0.1"); + map.put("server.tomcat.port-header", "x-my-forward-port"); + map.put("server.tomcat.protocol-header-https-value", "On"); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + this.customizer.customize(factory); + assertThat(factory.getEngineValves()).hasSize(1); + Valve valve = factory.getEngineValves().iterator().next(); + assertThat(valve).isInstanceOf(RemoteIpValve.class); + RemoteIpValve remoteIpValve = (RemoteIpValve) valve; + assertThat(remoteIpValve.getProtocolHeader()).isEqualTo("x-my-protocol-header"); + assertThat(remoteIpValve.getProtocolHeaderHttpsValue()).isEqualTo("On"); + assertThat(remoteIpValve.getRemoteIpHeader()).isEqualTo("x-my-remote-ip-header"); + assertThat(remoteIpValve.getPortHeader()).isEqualTo("x-my-forward-port"); + assertThat(remoteIpValve.getInternalProxies()).isEqualTo("192.168.0.1"); + } + + @Test + public void customTomcatAcceptCount() { + Map map = new HashMap<>(); + map.put("server.tomcat.accept-count", "10"); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(0); + this.customizer.customize(factory); + TomcatWebServer server = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class)); + server.start(); + try { + assertThat(((AbstractProtocol) server.getTomcat().getConnector() + .getProtocolHandler()).getAcceptCount()).isEqualTo(10); + } + finally { + server.stop(); + } + } + + @Test + public void customTomcatMaxConnections() { + Map map = new HashMap<>(); + map.put("server.tomcat.max-connections", "5"); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(0); + this.customizer.customize(factory); + TomcatWebServer server = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class)); + server.start(); + try { + assertThat(((AbstractProtocol) server.getTomcat().getConnector() + .getProtocolHandler()).getMaxConnections()).isEqualTo(5); + } + finally { + server.stop(); + } + } + + @Test + public void customTomcatMaxHttpPostSize() { + Map map = new HashMap<>(); + map.put("server.tomcat.max-http-post-size", "10000"); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(0); + this.customizer.customize(factory); + TomcatWebServer server = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class)); + server.start(); + try { + assertThat(server.getTomcat().getConnector().getMaxPostSize()) + .isEqualTo(10000); + } + finally { + server.stop(); + } + } + + @Test + public void customTomcatDisableMaxHttpPostSize() { + Map map = new HashMap<>(); + map.put("server.tomcat.max-http-post-size", "-1"); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(0); + this.customizer.customize(factory); + TomcatWebServer server = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class)); + server.start(); + try { + assertThat(server.getTomcat().getConnector().getMaxPostSize()).isEqualTo(-1); + } + finally { + server.stop(); + } + } + + @Test + public void testCustomizeTomcatMinSpareThreads() { + Map map = new HashMap<>(); + map.put("server.tomcat.min-spare-threads", "10"); + bindProperties(map); + assertThat(this.properties.getTomcat().getMinSpareThreads()).isEqualTo(10); + } + + @Test + public void customTomcatStaticResourceCacheTtl() { + Map map = new HashMap<>(); + map.put("server.tomcat.resource.cache-ttl", "10000"); + bindProperties(map); + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(0); + this.customizer.customize(factory); + TomcatWebServer server = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class)); + server.start(); + try { + Tomcat tomcat = server.getTomcat(); + Context context = (Context) tomcat.getHost().findChildren()[0]; + assertThat(context.getResources().getCacheTtl()).isEqualTo(10000L); + } + finally { + server.stop(); + } + } + + private void bindProperties(Map map) { + ConfigurationPropertySource source = new MapConfigurationPropertySource(map); + new Binder(source).bind("server", Bindable.ofInstance(this.properties)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java index ac0df680af..628b2d0f71 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java @@ -54,7 +54,7 @@ public class ReactiveWebServerAutoConfigurationTests { .hasSize(1); assertThat(this.context.getBeansOfType(WebServerFactoryCustomizer.class)) .hasSize(1); - assertThat(this.context.getBeansOfType(DefaultReactiveWebServerCustomizer.class)) + assertThat(this.context.getBeansOfType(DefaultReactiveWebServerFactoryCustomizer.class)) .hasSize(1); } diff --git a/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml b/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml index 02cf936565..9b01119636 100644 --- a/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml +++ b/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml @@ -14,6 +14,9 @@ + + + diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java new file mode 100644 index 0000000000..f686173d72 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.web.embedded.tomcat; + +import java.io.File; +import java.nio.charset.Charset; + +import org.apache.catalina.Context; +import org.apache.catalina.Engine; +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Connector; + +/** + * Web Server Factory configuration for Tomcat-specific features. + * + * @author Brian Clozel + * @since 2.0.0 + * @see TomcatServletWebServerFactory + * @see TomcatReactiveWebServerFactory + */ +public interface ConfigurableTomcatWebServerFactory { + + /** + * Set the Tomcat base directory. If not specified a temporary directory will be used. + * @param baseDirectory the tomcat base directory + */ + void setBaseDirectory(File baseDirectory); + + /** + * Sets the background processor delay in seconds. + * @param delay the delay in seconds + */ + void setBackgroundProcessorDelay(int delay); + + /** + * Add {@link Valve}s that should be applied to the Tomcat {@link Engine}. + * @param engineValves the valves to add + */ + void addEngineValves(Valve... engineValves); + + /** + * Add {@link TomcatConnectorCustomizer}s that should be added to the Tomcat + * {@link Connector}. + * @param tomcatConnectorCustomizers the customizers to add + */ + void addConnectorCustomizers(TomcatConnectorCustomizer... tomcatConnectorCustomizers); + + /** + * Add {@link TomcatContextCustomizer}s that should be added to the Tomcat + * {@link Context}. + * @param tomcatContextCustomizers the customizers to add + */ + void addContextCustomizers(TomcatContextCustomizer... tomcatContextCustomizers); + + /** + * Set the character encoding to use for URL decoding. If not specified 'UTF-8' will + * be used. + * @param uriEncoding the uri encoding to set + */ + void setUriEncoding(Charset uriEncoding); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatConnectorCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatConnectorCustomizer.java index c6c8e84cc9..4437c72ea8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatConnectorCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatConnectorCustomizer.java @@ -22,7 +22,7 @@ import org.apache.catalina.connector.Connector; * Callback interface that can be used to customize a Tomcat {@link Connector}. * * @author Dave Syer - * @see TomcatServletWebServerFactory + * @see ConfigurableTomcatWebServerFactory * @since 2.0.0 */ @FunctionalInterface diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java index 455708f991..7b9b86162f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java @@ -17,6 +17,8 @@ package org.springframework.boot.web.embedded.tomcat; import java.io.File; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -24,14 +26,17 @@ import java.util.Collections; import java.util.List; import org.apache.catalina.Context; +import org.apache.catalina.Engine; import org.apache.catalina.Host; import org.apache.catalina.LifecycleListener; +import org.apache.catalina.Valve; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.AbstractProtocol; import org.apache.coyote.http2.Http2Protocol; +import org.apache.tomcat.util.scan.StandardJarScanFilter; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; @@ -48,14 +53,19 @@ import org.springframework.util.StringUtils; * @author Brian Clozel * @since 2.0.0 */ -public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory { +public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory + implements ConfigurableTomcatWebServerFactory { + + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; /** * The class name of default protocol used. */ public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol"; - private String protocol = DEFAULT_PROTOCOL; + private File baseDirectory; + + private List engineValves = new ArrayList<>(); private List contextLifecycleListeners = new ArrayList<>( Collections.singleton(new AprLifecycleListener())); @@ -64,6 +74,13 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac private List tomcatConnectorCustomizers = new ArrayList<>(); + private String protocol = DEFAULT_PROTOCOL; + + private Charset uriEncoding = DEFAULT_CHARSET; + + private int backgroundProcessorDelay; + + /** * Create a new {@link TomcatServletWebServerFactory} instance. */ @@ -81,22 +98,26 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac @Override public WebServer getWebServer(HttpHandler httpHandler) { - Tomcat tomcatServer = createTomcatServer(); - TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler); - prepareContext(tomcatServer.getHost(), servlet); - return new TomcatWebServer(tomcatServer, getPort() >= 0); - } - - private Tomcat createTomcatServer() { Tomcat tomcat = new Tomcat(); - File baseDir = createTempDir("tomcat"); + File baseDir = (this.baseDirectory != null ? this.baseDirectory + : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); - return tomcat; + configureEngine(tomcat.getEngine()); + TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler); + prepareContext(tomcat.getHost(), servlet); + return new TomcatWebServer(tomcat, getPort() >= 0); + } + + private void configureEngine(Engine engine) { + engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay); + for (Valve valve : this.engineValves) { + engine.getPipeline().addValve(valve); + } } protected void prepareContext(Host host, TomcatHttpHandlerAdapter servlet) { @@ -106,6 +127,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new Tomcat.FixContextListener()); context.setParentClassLoader(ClassUtils.getDefaultClassLoader()); + skipAllTldScanning(context); WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); @@ -116,6 +138,12 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac host.addChild(context); } + private void skipAllTldScanning(TomcatEmbeddedContext context) { + StandardJarScanFilter filter = new StandardJarScanFilter(); + filter.setTldSkip("*.jar"); + context.getJarScanner().setJarScanFilter(filter); + } + /** * Configure the Tomcat {@link Context}. * @param context the Tomcat context @@ -135,6 +163,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac if (connector.getProtocolHandler() instanceof AbstractProtocol) { customizeProtocol((AbstractProtocol) connector.getProtocolHandler()); } + if (getUriEncoding() != null) { + connector.setURIEncoding(getUriEncoding().name()); + } // Don't bind to the socket prematurely if ApplicationContext is slow to start connector.setProperty("bindOnInit", "false"); if (getSsl() != null && getSsl().isEnabled()) { @@ -161,6 +192,16 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac } } + @Override + public void setBaseDirectory(File baseDirectory) { + this.baseDirectory = baseDirectory; + } + + @Override + public void setBackgroundProcessorDelay(int delay) { + this.backgroundProcessorDelay = delay; + } + /** * Set {@link TomcatContextCustomizer}s that should be applied to the Tomcat * {@link Context}. Calling this method will replace any existing customizers. @@ -187,6 +228,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac * {@link Context}. * @param tomcatContextCustomizers the customizers to add */ + @Override public void addContextCustomizers( TomcatContextCustomizer... tomcatContextCustomizers) { Assert.notNull(tomcatContextCustomizers, @@ -211,6 +253,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac * {@link Connector}. * @param tomcatConnectorCustomizers the customizers to add */ + @Override public void addConnectorCustomizers( TomcatConnectorCustomizer... tomcatConnectorCustomizers) { Assert.notNull(tomcatConnectorCustomizers, @@ -227,6 +270,39 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac return this.tomcatConnectorCustomizers; } + @Override + public void addEngineValves(Valve... engineValves) { + Assert.notNull(engineValves, "Valves must not be null"); + this.engineValves.addAll(Arrays.asList(engineValves)); + } + + /** + * Returns a mutable collection of the {@link Valve}s that will be applied to the + * Tomcat {@link Engine}. + * @return the engine valves that will be applied + */ + public List getEngineValves() { + return this.engineValves; + } + + /** + * Set the character encoding to use for URL decoding. If not specified 'UTF-8' will + * be used. + * @param uriEncoding the uri encoding to set + */ + @Override + public void setUriEncoding(Charset uriEncoding) { + this.uriEncoding = uriEncoding; + } + + /** + * Returns the character encoding to use for URL decoding. + * @return the URI encoding + */ + public Charset getUriEncoding() { + return this.uriEncoding; + } + /** * Set {@link LifecycleListener}s that should be applied to the Tomcat {@link Context}. * Calling this method will replace any existing listeners. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java index 2917c47fa3..bcf27b28e6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java @@ -96,7 +96,7 @@ import org.springframework.util.StringUtils; * @see TomcatWebServer */ public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory - implements ResourceLoaderAware { + implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; @@ -418,10 +418,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto this.resourceLoader = resourceLoader; } - /** - * Set the Tomcat base directory. If not specified a temporary directory will be used. - * @param baseDirectory the tomcat base directory - */ + @Override public void setBaseDirectory(File baseDirectory) { this.baseDirectory = baseDirectory; } @@ -483,10 +480,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto return this.engineValves; } - /** - * Add {@link Valve}s that should be applied to the Tomcat {@link Engine}. - * @param engineValves the valves to add - */ + @Override public void addEngineValves(Valve... engineValves) { Assert.notNull(engineValves, "Valves must not be null"); this.engineValves.addAll(Arrays.asList(engineValves)); @@ -574,11 +568,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto return this.tomcatContextCustomizers; } - /** - * Add {@link TomcatContextCustomizer}s that should be added to the Tomcat - * {@link Context}. - * @param tomcatContextCustomizers the customizers to add - */ + @Override public void addContextCustomizers( TomcatContextCustomizer... tomcatContextCustomizers) { Assert.notNull(tomcatContextCustomizers, @@ -598,11 +588,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto this.tomcatConnectorCustomizers = new ArrayList<>(tomcatConnectorCustomizers); } - /** - * Add {@link TomcatConnectorCustomizer}s that should be added to the Tomcat - * {@link Connector}. - * @param tomcatConnectorCustomizers the customizers to add - */ + @Override public void addConnectorCustomizers( TomcatConnectorCustomizer... tomcatConnectorCustomizers) { Assert.notNull(tomcatConnectorCustomizers, @@ -637,11 +623,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto return this.additionalTomcatConnectors; } - /** - * Set the character encoding to use for URL decoding. If not specified 'UTF-8' will - * be used. - * @param uriEncoding the uri encoding to set - */ + @Override public void setUriEncoding(Charset uriEncoding) { this.uriEncoding = uriEncoding; } @@ -654,11 +636,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto return this.uriEncoding; } - /** - * Sets the background processor delay in seconds. - * @param delay the delay in seconds - * @since 1.4.1 - */ + @Override public void setBackgroundProcessorDelay(int delay) { this.backgroundProcessorDelay = delay; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java index 875ce5c372..0f780611e3 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -42,7 +42,8 @@ import org.springframework.util.Assert; /** * {@link WebServer} that can be used to control a Tomcat web server. Usually this class - * should be created using the {@link TomcatReactiveWebServerFactory} and not directly. + * should be created using the {@link TomcatReactiveWebServerFactory} + * of {@link TomcatServletWebServerFactory}, but not directly. * * @author Brian Clozel * @author Kristine Jetzke