diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 40ea8a3dc45..af368d9a6f7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -646,6 +646,12 @@ public class ServerProperties */ 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. */ @@ -742,6 +748,14 @@ public class ServerProperties this.portHeader = portHeader; } + public Boolean getRedirectContextRoot() { + return this.redirectContextRoot; + } + + public void setRedirectContextRoot(Boolean redirectContextRoot) { + this.redirectContextRoot = redirectContextRoot; + } + public String getRemoteIpHeader() { return this.remoteIpHeader; } @@ -789,6 +803,9 @@ public class ServerProperties customizeConnectionTimeout(factory, serverProperties.getConnectionTimeout()); } + if (this.redirectContextRoot != null) { + customizeRedirectContextRoot(factory, this.redirectContextRoot); + } } private void customizeConnectionTimeout( @@ -911,6 +928,19 @@ public class ServerProperties 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 { /** diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index 4e609ff193e..af1a0014469 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -29,6 +29,7 @@ import javax.servlet.ServletException; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; +import org.apache.catalina.Context; import org.apache.catalina.Valve; import org.apache.catalina.valves.RemoteIpValve; import org.junit.Before; @@ -41,6 +42,7 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; 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.undertow.UndertowEmbeddedServletContainerFactory; 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}"); } + @Test + public void redirectContextRootIsNotConfiguredByDefault() throws Exception { + bindProperties(new HashMap()); + ServerProperties.Tomcat tomcat = this.properties.getTomcat(); + assertThat(tomcat.getRedirectContextRoot()).isNull(); + } + + @Test + public void redirectContextRootCanBeConfigured() throws Exception { + Map map = new HashMap(); + 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 public void testTrailingSlashOfContextPathIsRemoved() { new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index cd197f28190..8eb852c0b09 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -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.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.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.uri-encoding=UTF-8 # Character encoding to use to decode the URI. server.undertow.accesslog.dir= # Undertow access log directory. diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index 32d44e10817..a7698213c3a 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -184,7 +184,6 @@ public class TomcatEmbeddedServletContainerFactory : ClassUtils.getDefaultClassLoader()); try { context.setUseRelativeRedirects(false); - context.setMapperContextRootRedirectEnabled(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.0.30. Continue