Commit d264af81 authored by Brian Clozel's avatar Brian Clozel

Apply server.tomcat.* config to reactive servers

This commit applies most `server.tomcat.*` configuration
properties to Tomcat when set up as a reactive web server.

Some Servlet-specific properties are not applied:

* server.tomcat.additional-tld-skip-patterns
* server.tomcat.redirect-context-root
* server.tomcat.use-relative-redirects

Fixes gh-11334
parent 4b59d5f5
...@@ -22,7 +22,7 @@ import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType; ...@@ -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.actuate.autoconfigure.web.server.ManagementServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; 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.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -58,7 +58,7 @@ public class ReactiveManagementChildContextConfiguration { ...@@ -58,7 +58,7 @@ public class ReactiveManagementChildContextConfiguration {
ManagementServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> { ManagementServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {
ReactiveManagementServerFactoryCustomizer(ListableBeanFactory beanFactory) { ReactiveManagementServerFactoryCustomizer(ListableBeanFactory beanFactory) {
super(beanFactory, DefaultReactiveWebServerCustomizer.class); super(beanFactory, DefaultReactiveWebServerFactoryCustomizer.class);
} }
} }
......
/*
* 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);
}
});
});
}
}
/*
* 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;
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -17,9 +17,13 @@ ...@@ -17,9 +17,13 @@
package org.springframework.boot.autoconfigure.web.reactive; package org.springframework.boot.autoconfigure.web.reactive;
import org.springframework.boot.autoconfigure.web.ServerProperties; 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.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
/** /**
* Default {@link WebServerFactoryCustomizer} for reactive servers. * Default {@link WebServerFactoryCustomizer} for reactive servers.
...@@ -27,12 +31,14 @@ import org.springframework.core.Ordered; ...@@ -27,12 +31,14 @@ import org.springframework.core.Ordered;
* @author Brian Clozel * @author Brian Clozel
* @since 2.0.0 * @since 2.0.0
*/ */
public class DefaultReactiveWebServerCustomizer implements public class DefaultReactiveWebServerFactoryCustomizer implements
WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory>, Ordered { WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory>, EnvironmentAware, Ordered {
private final ServerProperties serverProperties; private final ServerProperties serverProperties;
public DefaultReactiveWebServerCustomizer(ServerProperties serverProperties) { private Environment environment;
public DefaultReactiveWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties; this.serverProperties = serverProperties;
} }
...@@ -42,21 +48,30 @@ public class DefaultReactiveWebServerCustomizer implements ...@@ -42,21 +48,30 @@ public class DefaultReactiveWebServerCustomizer implements
} }
@Override @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) { if (this.serverProperties.getPort() != null) {
server.setPort(this.serverProperties.getPort()); factory.setPort(this.serverProperties.getPort());
} }
if (this.serverProperties.getAddress() != null) { if (this.serverProperties.getAddress() != null) {
server.setAddress(this.serverProperties.getAddress()); factory.setAddress(this.serverProperties.getAddress());
} }
if (this.serverProperties.getSsl() != null) { if (this.serverProperties.getSsl() != null) {
server.setSsl(this.serverProperties.getSsl()); factory.setSsl(this.serverProperties.getSsl());
} }
if (this.serverProperties.getCompression() != null) { if (this.serverProperties.getCompression() != null) {
server.setCompression(this.serverProperties.getCompression()); factory.setCompression(this.serverProperties.getCompression());
} }
if (this.serverProperties.getHttp2() != null) { 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);
} }
} }
......
...@@ -59,9 +59,9 @@ public class ReactiveWebServerAutoConfiguration { ...@@ -59,9 +59,9 @@ public class ReactiveWebServerAutoConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@Bean @Bean
public DefaultReactiveWebServerCustomizer defaultReactiveWebServerCustomizer( public DefaultReactiveWebServerFactoryCustomizer defaultReactiveWebServerCustomizer(
ServerProperties serverProperties) { ServerProperties serverProperties) {
return new DefaultReactiveWebServerCustomizer(serverProperties); return new DefaultReactiveWebServerFactoryCustomizer(serverProperties);
} }
/** /**
......
...@@ -25,12 +25,6 @@ import javax.servlet.ServletException; ...@@ -25,12 +25,6 @@ import javax.servlet.ServletException;
import javax.servlet.SessionCookieConfig; import javax.servlet.SessionCookieConfig;
import io.undertow.UndertowOptions; 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.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
...@@ -43,10 +37,11 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; ...@@ -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;
import org.springframework.boot.autoconfigure.web.ServerProperties.Session; 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.cloud.CloudPlatform;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; 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.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.server.WebServerFactoryCustomizer;
...@@ -57,7 +52,6 @@ import org.springframework.context.EnvironmentAware; ...@@ -57,7 +52,6 @@ import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/** /**
* Default {@link WebServerFactoryCustomizer} for {@link ServerProperties}. * Default {@link WebServerFactoryCustomizer} for {@link ServerProperties}.
...@@ -126,8 +120,9 @@ public class DefaultServletWebServerFactoryCustomizer ...@@ -126,8 +120,9 @@ public class DefaultServletWebServerFactoryCustomizer
} }
factory.setServerHeader(this.serverProperties.getServerHeader()); factory.setServerHeader(this.serverProperties.getServerHeader());
if (factory instanceof TomcatServletWebServerFactory) { if (factory instanceof TomcatServletWebServerFactory) {
TomcatCustomizer.customizeTomcat(this.serverProperties, this.environment, TomcatServletWebServerFactory tomcatFactory = (TomcatServletWebServerFactory) factory;
(TomcatServletWebServerFactory) factory); TomcatCustomizer.customizeTomcat(this.serverProperties, this.environment, tomcatFactory);
TomcatServletCustomizer.customizeTomcat(this.serverProperties, this.environment, tomcatFactory);
} }
if (factory instanceof JettyServletWebServerFactory) { if (factory instanceof JettyServletWebServerFactory) {
JettyCustomizer.customizeJetty(this.serverProperties, this.environment, JettyCustomizer.customizeJetty(this.serverProperties, this.environment,
...@@ -150,7 +145,7 @@ public class DefaultServletWebServerFactoryCustomizer ...@@ -150,7 +145,7 @@ public class DefaultServletWebServerFactoryCustomizer
return serverProperties.isUseForwardHeaders(); return serverProperties.isUseForwardHeaders();
} }
CloudPlatform platform = CloudPlatform.getActive(environment); CloudPlatform platform = CloudPlatform.getActive(environment);
return (platform == null ? false : platform.isUsingForwardHeaders()); return platform != null && platform.isUsingForwardHeaders();
} }
/** /**
...@@ -214,43 +209,14 @@ public class DefaultServletWebServerFactoryCustomizer ...@@ -214,43 +209,14 @@ public class DefaultServletWebServerFactoryCustomizer
} }
private static class TomcatCustomizer { private static class TomcatServletCustomizer {
public static void customizeTomcat(ServerProperties serverProperties, public static void customizeTomcat(ServerProperties serverProperties,
Environment environment, TomcatServletWebServerFactory factory) { Environment environment, TomcatServletWebServerFactory factory) {
ServerProperties.Tomcat tomcatProperties = serverProperties.getTomcat(); ServerProperties.Tomcat tomcatProperties = serverProperties.getTomcat();
if (tomcatProperties.getBasedir() != null) { if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
factory.setBaseDirectory(tomcatProperties.getBasedir()); factory.getTldSkipPatterns()
} .addAll(tomcatProperties.getAdditionalTldSkipPatterns());
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.getRedirectContextRoot() != null) { if (tomcatProperties.getRedirectContextRoot() != null) {
customizeRedirectContextRoot(factory, customizeRedirectContextRoot(factory,
...@@ -260,162 +226,19 @@ public class DefaultServletWebServerFactoryCustomizer ...@@ -260,162 +226,19 @@ public class DefaultServletWebServerFactoryCustomizer
customizeUseRelativeRedirects(factory, customizeUseRelativeRedirects(factory,
tomcatProperties.getUseRelativeRedirects()); 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( private static void customizeRedirectContextRoot(
TomcatServletWebServerFactory factory, boolean redirectContextRoot) { ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
factory.addContextCustomizers((context) -> context factory.addContextCustomizers((context) -> context
.setMapperContextRootRedirectEnabled(redirectContextRoot)); .setMapperContextRootRedirectEnabled(redirectContextRoot));
} }
private static void customizeUseRelativeRedirects( private static void customizeUseRelativeRedirects(
TomcatServletWebServerFactory factory, boolean useRelativeRedirects) { ConfigurableTomcatWebServerFactory factory, boolean useRelativeRedirects) {
factory.addContextCustomizers( factory.addContextCustomizers(
(context) -> context.setUseRelativeRedirects(useRelativeRedirects)); (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 { private static class UndertowCustomizer {
......
/*
* 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);
}
}
/*
* 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("server", Bindable.ofInstance(this.properties));
}
}
...@@ -54,7 +54,7 @@ public class ReactiveWebServerAutoConfigurationTests { ...@@ -54,7 +54,7 @@ public class ReactiveWebServerAutoConfigurationTests {
.hasSize(1); .hasSize(1);
assertThat(this.context.getBeansOfType(WebServerFactoryCustomizer.class)) assertThat(this.context.getBeansOfType(WebServerFactoryCustomizer.class))
.hasSize(1); .hasSize(1);
assertThat(this.context.getBeansOfType(DefaultReactiveWebServerCustomizer.class)) assertThat(this.context.getBeansOfType(DefaultReactiveWebServerFactoryCustomizer.class))
.hasSize(1); .hasSize(1);
} }
......
...@@ -14,6 +14,9 @@ ...@@ -14,6 +14,9 @@
<subpackage name="client"> <subpackage name="client">
<allow pkg="org.springframework.boot.web.client" /> <allow pkg="org.springframework.boot.web.client" />
</subpackage> </subpackage>
<subpackage name="embedded.tomcat">
<allow pkg="org.springframework.boot.web.embedded.tomcat" />
</subpackage>
<subpackage name="servlet"> <subpackage name="servlet">
<allow pkg="javax.servlet" /> <allow pkg="javax.servlet" />
<allow pkg="org.springframework.boot.web.embedded" /> <allow pkg="org.springframework.boot.web.embedded" />
......
/*
* 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);
}
...@@ -22,7 +22,7 @@ import org.apache.catalina.connector.Connector; ...@@ -22,7 +22,7 @@ import org.apache.catalina.connector.Connector;
* Callback interface that can be used to customize a Tomcat {@link Connector}. * Callback interface that can be used to customize a Tomcat {@link Connector}.
* *
* @author Dave Syer * @author Dave Syer
* @see TomcatServletWebServerFactory * @see ConfigurableTomcatWebServerFactory
* @since 2.0.0 * @since 2.0.0
*/ */
@FunctionalInterface @FunctionalInterface
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
package org.springframework.boot.web.embedded.tomcat; package org.springframework.boot.web.embedded.tomcat;
import java.io.File; import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
...@@ -24,14 +26,17 @@ import java.util.Collections; ...@@ -24,14 +26,17 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.catalina.Context; import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host; import org.apache.catalina.Host;
import org.apache.catalina.LifecycleListener; import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector; import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.AbstractProtocol; import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.http2.Http2Protocol; 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.AbstractReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
...@@ -48,14 +53,19 @@ import org.springframework.util.StringUtils; ...@@ -48,14 +53,19 @@ import org.springframework.util.StringUtils;
* @author Brian Clozel * @author Brian Clozel
* @since 2.0.0 * @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. * The class name of default protocol used.
*/ */
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol"; public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private String protocol = DEFAULT_PROTOCOL; private File baseDirectory;
private List<Valve> engineValves = new ArrayList<>();
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>( private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>(
Collections.singleton(new AprLifecycleListener())); Collections.singleton(new AprLifecycleListener()));
...@@ -64,6 +74,13 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -64,6 +74,13 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<>(); private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<>();
private String protocol = DEFAULT_PROTOCOL;
private Charset uriEncoding = DEFAULT_CHARSET;
private int backgroundProcessorDelay;
/** /**
* Create a new {@link TomcatServletWebServerFactory} instance. * Create a new {@link TomcatServletWebServerFactory} instance.
*/ */
...@@ -81,22 +98,26 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -81,22 +98,26 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
@Override @Override
public WebServer getWebServer(HttpHandler httpHandler) { 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(); Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat"); File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath()); tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol); Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector); tomcat.getService().addConnector(connector);
customizeConnector(connector); customizeConnector(connector);
tomcat.setConnector(connector); tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false); 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) { protected void prepareContext(Host host, TomcatHttpHandlerAdapter servlet) {
...@@ -106,6 +127,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -106,6 +127,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
context.setDocBase(docBase.getAbsolutePath()); context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new Tomcat.FixContextListener()); context.addLifecycleListener(new Tomcat.FixContextListener());
context.setParentClassLoader(ClassUtils.getDefaultClassLoader()); context.setParentClassLoader(ClassUtils.getDefaultClassLoader());
skipAllTldScanning(context);
WebappLoader loader = new WebappLoader(context.getParentClassLoader()); WebappLoader loader = new WebappLoader(context.getParentClassLoader());
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true); loader.setDelegate(true);
...@@ -116,6 +138,12 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -116,6 +138,12 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
host.addChild(context); host.addChild(context);
} }
private void skipAllTldScanning(TomcatEmbeddedContext context) {
StandardJarScanFilter filter = new StandardJarScanFilter();
filter.setTldSkip("*.jar");
context.getJarScanner().setJarScanFilter(filter);
}
/** /**
* Configure the Tomcat {@link Context}. * Configure the Tomcat {@link Context}.
* @param context the Tomcat context * @param context the Tomcat context
...@@ -135,6 +163,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -135,6 +163,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
if (connector.getProtocolHandler() instanceof AbstractProtocol) { if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler()); customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
} }
if (getUriEncoding() != null) {
connector.setURIEncoding(getUriEncoding().name());
}
// Don't bind to the socket prematurely if ApplicationContext is slow to start // Don't bind to the socket prematurely if ApplicationContext is slow to start
connector.setProperty("bindOnInit", "false"); connector.setProperty("bindOnInit", "false");
if (getSsl() != null && getSsl().isEnabled()) { if (getSsl() != null && getSsl().isEnabled()) {
...@@ -161,6 +192,16 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -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 * Set {@link TomcatContextCustomizer}s that should be applied to the Tomcat
* {@link Context}. Calling this method will replace any existing customizers. * {@link Context}. Calling this method will replace any existing customizers.
...@@ -187,6 +228,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -187,6 +228,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
* {@link Context}. * {@link Context}.
* @param tomcatContextCustomizers the customizers to add * @param tomcatContextCustomizers the customizers to add
*/ */
@Override
public void addContextCustomizers( public void addContextCustomizers(
TomcatContextCustomizer... tomcatContextCustomizers) { TomcatContextCustomizer... tomcatContextCustomizers) {
Assert.notNull(tomcatContextCustomizers, Assert.notNull(tomcatContextCustomizers,
...@@ -211,6 +253,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -211,6 +253,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
* {@link Connector}. * {@link Connector}.
* @param tomcatConnectorCustomizers the customizers to add * @param tomcatConnectorCustomizers the customizers to add
*/ */
@Override
public void addConnectorCustomizers( public void addConnectorCustomizers(
TomcatConnectorCustomizer... tomcatConnectorCustomizers) { TomcatConnectorCustomizer... tomcatConnectorCustomizers) {
Assert.notNull(tomcatConnectorCustomizers, Assert.notNull(tomcatConnectorCustomizers,
...@@ -227,6 +270,39 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -227,6 +270,39 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
return this.tomcatConnectorCustomizers; 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<Valve> 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}. * Set {@link LifecycleListener}s that should be applied to the Tomcat {@link Context}.
* Calling this method will replace any existing listeners. * Calling this method will replace any existing listeners.
......
...@@ -96,7 +96,7 @@ import org.springframework.util.StringUtils; ...@@ -96,7 +96,7 @@ import org.springframework.util.StringUtils;
* @see TomcatWebServer * @see TomcatWebServer
*/ */
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ResourceLoaderAware { implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
...@@ -418,10 +418,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -418,10 +418,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
} }
/** @Override
* Set the Tomcat base directory. If not specified a temporary directory will be used.
* @param baseDirectory the tomcat base directory
*/
public void setBaseDirectory(File baseDirectory) { public void setBaseDirectory(File baseDirectory) {
this.baseDirectory = baseDirectory; this.baseDirectory = baseDirectory;
} }
...@@ -483,10 +480,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -483,10 +480,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
return this.engineValves; return this.engineValves;
} }
/** @Override
* Add {@link Valve}s that should be applied to the Tomcat {@link Engine}.
* @param engineValves the valves to add
*/
public void addEngineValves(Valve... engineValves) { public void addEngineValves(Valve... engineValves) {
Assert.notNull(engineValves, "Valves must not be null"); Assert.notNull(engineValves, "Valves must not be null");
this.engineValves.addAll(Arrays.asList(engineValves)); this.engineValves.addAll(Arrays.asList(engineValves));
...@@ -574,11 +568,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -574,11 +568,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
return this.tomcatContextCustomizers; return this.tomcatContextCustomizers;
} }
/** @Override
* Add {@link TomcatContextCustomizer}s that should be added to the Tomcat
* {@link Context}.
* @param tomcatContextCustomizers the customizers to add
*/
public void addContextCustomizers( public void addContextCustomizers(
TomcatContextCustomizer... tomcatContextCustomizers) { TomcatContextCustomizer... tomcatContextCustomizers) {
Assert.notNull(tomcatContextCustomizers, Assert.notNull(tomcatContextCustomizers,
...@@ -598,11 +588,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -598,11 +588,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
this.tomcatConnectorCustomizers = new ArrayList<>(tomcatConnectorCustomizers); this.tomcatConnectorCustomizers = new ArrayList<>(tomcatConnectorCustomizers);
} }
/** @Override
* Add {@link TomcatConnectorCustomizer}s that should be added to the Tomcat
* {@link Connector}.
* @param tomcatConnectorCustomizers the customizers to add
*/
public void addConnectorCustomizers( public void addConnectorCustomizers(
TomcatConnectorCustomizer... tomcatConnectorCustomizers) { TomcatConnectorCustomizer... tomcatConnectorCustomizers) {
Assert.notNull(tomcatConnectorCustomizers, Assert.notNull(tomcatConnectorCustomizers,
...@@ -637,11 +623,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -637,11 +623,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
return this.additionalTomcatConnectors; return this.additionalTomcatConnectors;
} }
/** @Override
* Set the character encoding to use for URL decoding. If not specified 'UTF-8' will
* be used.
* @param uriEncoding the uri encoding to set
*/
public void setUriEncoding(Charset uriEncoding) { public void setUriEncoding(Charset uriEncoding) {
this.uriEncoding = uriEncoding; this.uriEncoding = uriEncoding;
} }
...@@ -654,11 +636,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto ...@@ -654,11 +636,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
return this.uriEncoding; return this.uriEncoding;
} }
/** @Override
* Sets the background processor delay in seconds.
* @param delay the delay in seconds
* @since 1.4.1
*/
public void setBackgroundProcessorDelay(int delay) { public void setBackgroundProcessorDelay(int delay) {
this.backgroundProcessorDelay = delay; this.backgroundProcessorDelay = delay;
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -42,7 +42,8 @@ import org.springframework.util.Assert; ...@@ -42,7 +42,8 @@ import org.springframework.util.Assert;
/** /**
* {@link WebServer} that can be used to control a Tomcat web server. Usually this class * {@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 Brian Clozel
* @author Kristine Jetzke * @author Kristine Jetzke
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment