From 1270af90c2ab3cfc951b67eed125707709027d9b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 11 Aug 2020 09:59:06 +0100 Subject: [PATCH] Only enable full path optimization when there's one DispatcherServlet Previously, UrlPathHelper's full path optimization was enabled when there was a dispatcher servlet mapped to /. The UrlPathHelper is used across Spring MVC and if there are multiple dispatcher servlets they all share the sample UrlPathHelper. This meant that any additional dispatcher servlets mapping to locations other than / would not be able to map requests correctly as the UrlPathHelper would use the full path, ignoring the url mapping of the dispatcher servlet. This commit updates the MVC auto-configuration so that use of the full path is only enabled if there's a single dispatcher servlet registration. Fixes gh-22682 --- .../web/servlet/WebMvcAutoConfiguration.java | 14 ++++++- .../servlet/WebMvcAutoConfigurationTests.java | 39 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index 2167937eeea..051ad09a2e7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -56,6 +56,7 @@ import org.springframework.boot.autoconfigure.web.format.WebConversionService; import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.convert.ApplicationConversionService; +import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter; import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; @@ -191,18 +192,22 @@ public class WebMvcAutoConfiguration { private final ObjectProvider dispatcherServletPath; + private final ObjectProvider> servletRegistrations; + final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider messageConvertersProvider, ObjectProvider resourceHandlerRegistrationCustomizerProvider, - ObjectProvider dispatcherServletPath) { + ObjectProvider dispatcherServletPath, + ObjectProvider> servletRegistrations) { this.resourceProperties = resourceProperties; this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.dispatcherServletPath = dispatcherServletPath; + this.servletRegistrations = servletRegistrations; } @Override @@ -234,7 +239,7 @@ public class WebMvcAutoConfiguration { this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern()); this.dispatcherServletPath.ifAvailable((dispatcherPath) -> { String servletUrlMapping = dispatcherPath.getServletUrlMapping(); - if (servletUrlMapping.equals("/")) { + if (servletUrlMapping.equals("/") && singleDispatcherServlet()) { UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setAlwaysUseFullPath(true); configurer.setUrlPathHelper(urlPathHelper); @@ -242,6 +247,11 @@ public class WebMvcAutoConfiguration { }); } + private boolean singleDispatcherServlet() { + return this.servletRegistrations.stream().map(ServletRegistrationBean::getServlet) + .filter(DispatcherServlet.class::isInstance).count() == 1; + } + @Override @SuppressWarnings("deprecation") public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index b53f94b6fb3..ef4a29ae22e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -52,6 +52,7 @@ import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor; import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ApplicationContext; @@ -84,6 +85,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.FormContentFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.filter.RequestContextFilter; +import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; @@ -866,6 +868,23 @@ class WebMvcAutoConfigurationTests { }); } + @Test + void urlPathHelperDoesNotUseFullPathWithAdditionalDispatcherServlet() { + this.contextRunner.withUserConfiguration(AdditionalDispatcherServletConfiguration.class).run((context) -> { + UrlPathHelper urlPathHelper = context.getBean(UrlPathHelper.class); + assertThat(urlPathHelper).extracting("alwaysUseFullPath").isEqualTo(false); + }); + } + + @Test + void urlPathHelperDoesNotUseFullPathWithAdditionalUntypedDispatcherServlet() { + this.contextRunner.withUserConfiguration(AdditionalUntypedDispatcherServletConfiguration.class) + .run((context) -> { + UrlPathHelper urlPathHelper = context.getBean(UrlPathHelper.class); + assertThat(urlPathHelper).extracting("alwaysUseFullPath").isEqualTo(false); + }); + } + private void assertCacheControl(AssertableWebApplicationContext context) { Map handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class)); assertThat(handlerMap).hasSize(2); @@ -1248,4 +1267,24 @@ class WebMvcAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class AdditionalDispatcherServletConfiguration { + + @Bean + ServletRegistrationBean additionalDispatcherServlet() { + return new ServletRegistrationBean<>(new DispatcherServlet()); + } + + } + + @Configuration(proxyBeanMethods = false) + static class AdditionalUntypedDispatcherServletConfiguration { + + @Bean + ServletRegistrationBean additionalDispatcherServlet() { + return new ServletRegistrationBean<>(new DispatcherServlet()); + } + + } + }