Allow Tomcat context root redirect to be configured via the environment

This commit adds a new property, server.tomcat.redirect-context-root,
that can be used to configure the Tomcat Context’s 
mapperContextRootRedirectEnabled property. The default is to not apply
any configuration and, therefore, to use Tomcat’s default of true.

Closes gh-6248
This commit is contained in:
Andy Wilkinson 2016-06-29 19:47:57 +01:00
parent 05b9dd1cb9
commit 2b970f9efc
4 changed files with 57 additions and 1 deletions

View File

@ -646,6 +646,12 @@ public class ServerProperties
*/ */
private int maxHttpHeaderSize = 0; // bytes private int maxHttpHeaderSize = 0; // bytes
/**
* Whether requests to the context root should be redirected by appending a / to
* the path.
*/
private Boolean redirectContextRoot;
/** /**
* Character encoding to use to decode the URI. * Character encoding to use to decode the URI.
*/ */
@ -742,6 +748,14 @@ public class ServerProperties
this.portHeader = portHeader; this.portHeader = portHeader;
} }
public Boolean getRedirectContextRoot() {
return this.redirectContextRoot;
}
public void setRedirectContextRoot(Boolean redirectContextRoot) {
this.redirectContextRoot = redirectContextRoot;
}
public String getRemoteIpHeader() { public String getRemoteIpHeader() {
return this.remoteIpHeader; return this.remoteIpHeader;
} }
@ -789,6 +803,9 @@ public class ServerProperties
customizeConnectionTimeout(factory, customizeConnectionTimeout(factory,
serverProperties.getConnectionTimeout()); serverProperties.getConnectionTimeout());
} }
if (this.redirectContextRoot != null) {
customizeRedirectContextRoot(factory, this.redirectContextRoot);
}
} }
private void customizeConnectionTimeout( private void customizeConnectionTimeout(
@ -911,6 +928,19 @@ public class ServerProperties
factory.addContextValves(valve); factory.addContextValves(valve);
} }
private void customizeRedirectContextRoot(
TomcatEmbeddedServletContainerFactory factory,
final boolean redirectContextRoot) {
factory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
context.setMapperContextRootRedirectEnabled(redirectContextRoot);
}
});
}
public static class Accesslog { public static class Accesslog {
/** /**

View File

@ -29,6 +29,7 @@ import javax.servlet.ServletException;
import javax.servlet.SessionCookieConfig; import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode; import javax.servlet.SessionTrackingMode;
import org.apache.catalina.Context;
import org.apache.catalina.Valve; import org.apache.catalina.Valve;
import org.apache.catalina.valves.RemoteIpValve; import org.apache.catalina.valves.RemoteIpValve;
import org.junit.Before; import org.junit.Before;
@ -41,6 +42,7 @@ import org.springframework.beans.MutablePropertyValues;
import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.ServletContextInitializer;
@ -150,6 +152,30 @@ public class ServerPropertiesTests {
.isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); .isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
} }
@Test
public void redirectContextRootIsNotConfiguredByDefault() throws Exception {
bindProperties(new HashMap<String, String>());
ServerProperties.Tomcat tomcat = this.properties.getTomcat();
assertThat(tomcat.getRedirectContextRoot()).isNull();
}
@Test
public void redirectContextRootCanBeConfigured() throws Exception {
Map<String, String> map = new HashMap<String, String>();
map.put("server.tomcat.redirect-context-root", "false");
bindProperties(map);
ServerProperties.Tomcat tomcat = this.properties.getTomcat();
assertThat(tomcat.getRedirectContextRoot()).isEqualTo(false);
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(container);
Context context = mock(Context.class);
for (TomcatContextCustomizer customizer : container
.getTomcatContextCustomizers()) {
customizer.customize(context);
}
verify(context).setMapperContextRootRedirectEnabled(false);
}
@Test @Test
public void testTrailingSlashOfContextPathIsRemoved() { public void testTrailingSlashOfContextPathIsRemoved() {
new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues(

View File

@ -214,6 +214,7 @@ content into your application; rather pick only the properties that you need.
server.tomcat.port-header=X-Forwarded-Port # Name of the HTTP header used to override the original port value. server.tomcat.port-header=X-Forwarded-Port # Name of the HTTP header used to override the original port value.
server.tomcat.protocol-header= # Header that holds the incoming protocol, usually named "X-Forwarded-Proto". server.tomcat.protocol-header= # Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
server.tomcat.protocol-header-https-value=https # Value of the protocol header that indicates that the incoming request uses SSL. server.tomcat.protocol-header-https-value=https # Value of the protocol header that indicates that the incoming request uses SSL.
server.tomcat.redirect-context-root= # Whether requests to the context root should be redirected by appending a / to the path.
server.tomcat.remote-ip-header= # Name of the http header from which the remote ip is extracted. For instance `X-FORWARDED-FOR` server.tomcat.remote-ip-header= # Name of the http header from which the remote ip is extracted. For instance `X-FORWARDED-FOR`
server.tomcat.uri-encoding=UTF-8 # Character encoding to use to decode the URI. server.tomcat.uri-encoding=UTF-8 # Character encoding to use to decode the URI.
server.undertow.accesslog.dir= # Undertow access log directory. server.undertow.accesslog.dir= # Undertow access log directory.

View File

@ -184,7 +184,6 @@ public class TomcatEmbeddedServletContainerFactory
: ClassUtils.getDefaultClassLoader()); : ClassUtils.getDefaultClassLoader());
try { try {
context.setUseRelativeRedirects(false); context.setUseRelativeRedirects(false);
context.setMapperContextRootRedirectEnabled(true);
} }
catch (NoSuchMethodError ex) { catch (NoSuchMethodError ex) {
// Tomcat is < 8.0.30. Continue // Tomcat is < 8.0.30. Continue