From 9a9111af2132ff50afe4aa0704be4bce19cf76fa Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 24 Jul 2018 16:35:37 +0100 Subject: [PATCH] Support path discovery for main dispatcher servlet Add an `DispatcherServletPath` interface which provides a much more consistent way to discover the path of the main dispatcher servet. Prior to this commit, auto-configurations would often make use of the `ServerProperties` class to discover the dispatcher servlet path. This mechanism isn't very explicit and also makes it hard for us to relocate that property in Spring Boot 2.1. This commit also reverts most of fddc9e9c7e since it is now clear that the supporting multiple dispatcher servlet paths will be much more involved that we originally anticipated. Closes gh-13834 --- ...ndpointManagementContextConfiguration.java | 28 ++---- .../security/servlet/EndpointRequest.java | 43 ++++----- ...bMvcEndpointChildContextConfiguration.java | 15 ++-- ...ntManagementContextConfigurationTests.java | 27 ++---- .../servlet/EndpointRequestTests.java | 40 ++++----- ...ndpointChildContextConfigurationTests.java | 10 +-- .../web/ServletEndpointRegistrar.java | 36 ++------ .../web/ServletEndpointRegistrarTests.java | 65 +------------- .../servlet/StaticResourceRequest.java | 21 ++--- .../autoconfigure/web/ServerProperties.java | 38 ++++++++ .../DispatcherServletAutoConfiguration.java | 18 +--- .../web/servlet/DispatcherServletPath.java | 89 +++++++++++++++++++ .../DispatcherServletPathProvider.java | 7 +- .../DispatcherServletRegistrationBean.java | 67 ++++++++++++++ .../error/ErrorMvcAutoConfiguration.java | 18 ++-- .../servlet/StaticResourceRequestTests.java | 6 ++ .../web/ServerPropertiesTests.java | 2 + ...spatcherServletAutoConfigurationTests.java | 45 ++++------ .../servlet/DispatcherServletPathTests.java | 88 ++++++++++++++++++ ...ispatcherServletRegistrationBeanTests.java | 76 ++++++++++++++++ .../error/ErrorMvcAutoConfigurationTests.java | 5 +- .../web/servlet/MockMvcAutoConfiguration.java | 17 +++- 22 files changed, 501 insertions(+), 260 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBean.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBeanTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java index 9c97916609..2eb70eba71 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java @@ -16,9 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; -import java.util.Set; -import java.util.stream.Collectors; - import org.glassfish.jersey.server.ResourceConfig; import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; @@ -30,11 +27,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; import org.springframework.web.servlet.DispatcherServlet; /** @@ -72,27 +68,13 @@ public class ServletEndpointManagementContextConfiguration { public ServletEndpointRegistrar servletEndpointRegistrar( WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) { - DispatcherServletPathProvider servletPathProvider = this.context - .getBean(DispatcherServletPathProvider.class); - Set cleanedPaths = getServletPaths(properties, servletPathProvider); - return new ServletEndpointRegistrar(cleanedPaths, + DispatcherServletPath dispatcherServletPath = this.context + .getBean(DispatcherServletPath.class); + return new ServletEndpointRegistrar( + dispatcherServletPath.getRelativePath(properties.getBasePath()), servletEndpointsSupplier.getEndpoints()); } - private Set getServletPaths(WebEndpointProperties properties, - DispatcherServletPathProvider servletPathProvider) { - return servletPathProvider.getServletPaths().stream() - .map((p) -> cleanServletPath(p) + properties.getBasePath()) - .collect(Collectors.toSet()); - } - - private String cleanServletPath(String servletPath) { - if (StringUtils.hasText(servletPath) && servletPath.endsWith("/")) { - return servletPath.substring(0, servletPath.length() - 1); - } - return servletPath; - } - } @Configuration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java index b576b440df..9528a9404c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java @@ -33,7 +33,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -137,23 +137,20 @@ public final class EndpointRequest { private RequestMatcher createDelegate(WebApplicationContext context) { try { - Set servletPaths = getServletPaths(context); - RequestMatcherFactory requestMatcherFactory = new RequestMatcherFactory( - servletPaths); - return createDelegate(context, requestMatcherFactory); + String pathPrefix = getPathPrefix(context); + return createDelegate(context, new RequestMatcherFactory(pathPrefix)); } catch (NoSuchBeanDefinitionException ex) { return EMPTY_MATCHER; } } - private Set getServletPaths(WebApplicationContext context) { + private String getPathPrefix(WebApplicationContext context) { try { - return context.getBean(DispatcherServletPathProvider.class) - .getServletPaths(); + return context.getBean(DispatcherServletPath.class).getPrefix(); } catch (NoSuchBeanDefinitionException ex) { - return Collections.singleton(""); + return ""; } } @@ -225,7 +222,7 @@ public final class EndpointRequest { requestMatcherFactory, paths); if (this.includeLinks && StringUtils.hasText(pathMappedEndpoints.getBasePath())) { - delegateMatchers.addAll( + delegateMatchers.add( requestMatcherFactory.antPath(pathMappedEndpoints.getBasePath())); } return new OrRequestMatcher(delegateMatchers); @@ -258,8 +255,7 @@ public final class EndpointRequest { private List getDelegateMatchers( RequestMatcherFactory requestMatcherFactory, Set paths) { return paths.stream() - .flatMap( - (path) -> requestMatcherFactory.antPath(path, "/**").stream()) + .map((path) -> requestMatcherFactory.antPath(path, "/**")) .collect(Collectors.toList()); } @@ -276,9 +272,7 @@ public final class EndpointRequest { WebEndpointProperties properties = context .getBean(WebEndpointProperties.class); if (StringUtils.hasText(properties.getBasePath())) { - List matchers = requestMatcherFactory - .antPath(properties.getBasePath()); - return new OrRequestMatcher(matchers); + return requestMatcherFactory.antPath(properties.getBasePath()); } return EMPTY_MATCHER; } @@ -290,19 +284,18 @@ public final class EndpointRequest { */ private static class RequestMatcherFactory { - private final Set servletPaths = new LinkedHashSet<>(); + private final String prefix; - RequestMatcherFactory(Set servletPaths) { - this.servletPaths.addAll(servletPaths); + RequestMatcherFactory(String prefix) { + this.prefix = prefix; } - List antPath(String... parts) { - return this.servletPaths.stream() - .map((p) -> (StringUtils.hasText(p) && !p.equals("/") ? p : "")) - .distinct() - .map((path) -> Arrays.stream(parts) - .collect(Collectors.joining("", path, ""))) - .map(AntPathRequestMatcher::new).collect(Collectors.toList()); + public RequestMatcher antPath(String... parts) { + String pattern = this.prefix; + for (String part : parts) { + pattern += part; + } + return new AntPathRequestMatcher(pattern); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java index 52479598d7..568a12ff92 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.web.servlet; -import java.util.Collections; - import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType; @@ -27,7 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; import org.springframework.context.annotation.Bean; @@ -72,6 +70,12 @@ class WebMvcEndpointChildContextConfiguration { return dispatcherServlet; } + @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) + public DispatcherServletRegistrationBean dispatcherServletRegistrationBean( + DispatcherServlet dispatcherServlet) { + return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); + } + @Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME) public CompositeHandlerMapping compositeHandlerMapping() { return new CompositeHandlerMapping(); @@ -95,9 +99,4 @@ class WebMvcEndpointChildContextConfiguration { return new OrderedRequestContextFilter(); } - @Bean - public DispatcherServletPathProvider childDispatcherServletPathProvider() { - return () -> Collections.singleton(""); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java index 70cd7db506..51c76f18ad 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java @@ -17,15 +17,13 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; import org.glassfish.jersey.server.ResourceConfig; import org.junit.Test; import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -50,22 +48,18 @@ public class ServletEndpointManagementContextConfigurationTests { .withUserConfiguration(TestConfig.class); @Test - @SuppressWarnings("unchecked") public void contextShouldContainServletEndpointRegistrar() { FilteredClassLoader classLoader = new FilteredClassLoader(ResourceConfig.class); this.contextRunner.withClassLoader(classLoader).run((context) -> { assertThat(context).hasSingleBean(ServletEndpointRegistrar.class); ServletEndpointRegistrar bean = context .getBean(ServletEndpointRegistrar.class); - Set basePaths = (Set) ReflectionTestUtils.getField(bean, - "basePaths"); - assertThat(basePaths).containsExactlyInAnyOrder("/test/actuator", "/actuator", - "/foo/actuator"); + String basePath = (String) ReflectionTestUtils.getField(bean, "basePath"); + assertThat(basePath).isEqualTo("/test/actuator"); }); } @Test - @SuppressWarnings("unchecked") public void servletPathShouldNotAffectJerseyConfiguration() { FilteredClassLoader classLoader = new FilteredClassLoader( DispatcherServlet.class); @@ -73,9 +67,8 @@ public class ServletEndpointManagementContextConfigurationTests { assertThat(context).hasSingleBean(ServletEndpointRegistrar.class); ServletEndpointRegistrar bean = context .getBean(ServletEndpointRegistrar.class); - Set basePaths = (Set) ReflectionTestUtils.getField(bean, - "basePaths"); - assertThat(basePaths).containsExactly("/actuator"); + String basePath = (String) ReflectionTestUtils.getField(bean, "basePath"); + assertThat(basePath).isEqualTo("/actuator"); }); } @@ -97,14 +90,8 @@ public class ServletEndpointManagementContextConfigurationTests { } @Bean - public DispatcherServletPathProvider servletPathProvider() { - return () -> { - Set paths = new LinkedHashSet<>(); - paths.add("/"); - paths.add("/test"); - paths.add("/foo/"); - return paths; - }; + public DispatcherServletPath dispatcherServletPath() { + return () -> "/test"; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java index 20df4c8c57..d2682e30ea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java @@ -17,8 +17,6 @@ package org.springframework.boot.actuate.autoconfigure.security.servlet; import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -33,7 +31,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -78,12 +76,11 @@ public class EndpointRequestTests { @Test public void toAnyEndpointWhenServletPathNotEmptyShouldMatch() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); - assertMatcher(matcher, "/actuator", "/spring", "/admin") - .matches(Arrays.asList("/spring", "/admin"), "/actuator/foo"); - assertMatcher(matcher, "/actuator", "/spring", "/admin") - .matches(Arrays.asList("/spring", "/admin"), "/actuator/bar"); - assertMatcher(matcher, "/actuator", "/spring").matches(Arrays.asList("/spring"), - "/actuator"); + assertMatcher(matcher, "/actuator", "/spring").matches("/spring", + "/actuator/foo"); + assertMatcher(matcher, "/actuator", "/spring").matches("/spring", + "/actuator/bar"); + assertMatcher(matcher, "/actuator", "/spring").matches("/spring", "/actuator"); assertMatcher(matcher, "/actuator", "/spring").doesNotMatch("/spring", "/actuator/baz"); assertMatcher(matcher, "/actuator", "/spring").doesNotMatch("", "/actuator/foo"); @@ -92,10 +89,10 @@ public class EndpointRequestTests { @Test public void toAnyEndpointWhenDispatcherServletPathProviderNotAvailableUsesEmptyPath() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); - assertMatcher(matcher, "/actuator", (String) null).matches("/actuator/foo"); - assertMatcher(matcher, "/actuator", (String) null).matches("/actuator/bar"); - assertMatcher(matcher, "/actuator", (String) null).matches("/actuator"); - assertMatcher(matcher, "/actuator", (String) null).doesNotMatch("/actuator/baz"); + assertMatcher(matcher, "/actuator", null).matches("/actuator/foo"); + assertMatcher(matcher, "/actuator", null).matches("/actuator/bar"); + assertMatcher(matcher, "/actuator", null).matches("/actuator"); + assertMatcher(matcher, "/actuator", null).doesNotMatch("/actuator/baz"); } @Test @@ -222,8 +219,8 @@ public class EndpointRequestTests { } private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePath, - String... servletPaths) { - return assertMatcher(matcher, mockPathMappedEndpoints(basePath), servletPaths); + String servletPath) { + return assertMatcher(matcher, mockPathMappedEndpoints(basePath), servletPath); } private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { @@ -246,7 +243,7 @@ public class EndpointRequestTests { } private RequestMatcherAssert assertMatcher(RequestMatcher matcher, - PathMappedEndpoints pathMappedEndpoints, String... servletPaths) { + PathMappedEndpoints pathMappedEndpoints, String dispatcherServletPath) { StaticWebApplicationContext context = new StaticWebApplicationContext(); context.registerBean(WebEndpointProperties.class); if (pathMappedEndpoints != null) { @@ -257,10 +254,9 @@ public class EndpointRequestTests { properties.setBasePath(pathMappedEndpoints.getBasePath()); } } - if (servletPaths != null) { - DispatcherServletPathProvider pathProvider = () -> new LinkedHashSet<>( - Arrays.asList(servletPaths)); - context.registerBean(DispatcherServletPathProvider.class, () -> pathProvider); + if (dispatcherServletPath != null) { + DispatcherServletPath path = () -> dispatcherServletPath; + context.registerBean(DispatcherServletPath.class, () -> path); } return assertThat(new RequestMatcherAssert(context, matcher)); } @@ -280,8 +276,8 @@ public class EndpointRequestTests { matches(mockRequest(servletPath)); } - public void matches(List servletPaths, String pathInfo) { - servletPaths.forEach((p) -> matches(mockRequest(p, pathInfo))); + public void matches(String servletPath, String pathInfo) { + matches(mockRequest(servletPath, pathInfo)); } private void matches(HttpServletRequest request) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java index 61e5c59934..20ebfcdebd 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java @@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.web.servlet; import org.junit.Test; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; import org.springframework.context.annotation.Bean; @@ -64,12 +64,12 @@ public class WebMvcEndpointChildContextConfigurationTests { } @Test - public void contextShouldConfigureDispatcherServletPathProviderWithEmptyPath() { + public void contextShouldConfigureDispatcherServletPathWithRootPath() { this.contextRunner .withUserConfiguration(WebMvcEndpointChildContextConfiguration.class) - .run((context) -> assertThat(context - .getBean(DispatcherServletPathProvider.class).getServletPaths()) - .containsExactly("")); + .run((context) -> assertThat( + context.getBean(DispatcherServletPath.class).getPath()) + .isEqualTo("/")); } static class ExistingConfig { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java index 1a45fa1db9..26f761962b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java @@ -16,10 +16,7 @@ package org.springframework.boot.actuate.endpoint.web; -import java.util.Arrays; import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -30,7 +27,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; /** * {@link ServletContextInitializer} to register {@link ExposableServletEndpoint servlet @@ -44,24 +40,14 @@ public class ServletEndpointRegistrar implements ServletContextInitializer { private static final Log logger = LogFactory.getLog(ServletEndpointRegistrar.class); - private final Set basePaths = new LinkedHashSet<>(); + private final String basePath; private final Collection servletEndpoints; public ServletEndpointRegistrar(String basePath, Collection servletEndpoints) { Assert.notNull(servletEndpoints, "ServletEndpoints must not be null"); - this.basePaths.add((basePath != null ? basePath : "")); - this.servletEndpoints = servletEndpoints; - } - - public ServletEndpointRegistrar(Set basePaths, - Collection servletEndpoints) { - Assert.notNull(servletEndpoints, "ServletEndpoints must not be null"); - this.basePaths.addAll(basePaths); - if (CollectionUtils.isEmpty(this.basePaths)) { - this.basePaths.add(""); - } + this.basePath = (basePath != null ? basePath : ""); this.servletEndpoints = servletEndpoints; } @@ -74,24 +60,14 @@ public class ServletEndpointRegistrar implements ServletContextInitializer { private void register(ServletContext servletContext, ExposableServletEndpoint endpoint) { String name = endpoint.getId() + "-actuator-endpoint"; + String path = this.basePath + "/" + endpoint.getRootPath(); + String urlMapping = (path.endsWith("/") ? path + "*" : path + "/*"); EndpointServlet endpointServlet = endpoint.getEndpointServlet(); Dynamic registration = servletContext.addServlet(name, endpointServlet.getServlet()); - String[] urlMappings = getUrlMappings(endpoint.getRootPath()); - registration.addMapping(urlMappings); - if (logger.isInfoEnabled()) { - Arrays.stream(urlMappings).forEach( - (mapping) -> logger.info("Registered '" + mapping + "' to " + name)); - } + registration.addMapping(urlMapping); registration.setInitParameters(endpointServlet.getInitParameters()); - } - - private String[] getUrlMappings(String endpointPath) { - return this.basePaths.stream() - .map((basePath) -> (basePath != null ? basePath + "/" + endpointPath - : "/" + endpointPath)) - .distinct().map((path) -> (path.endsWith("/") ? path + "*" : path + "/*")) - .toArray(String[]::new); + logger.info("Registered '" + path + "' to " + name); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java index 9221133b6c..542f435bc2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java @@ -18,8 +18,6 @@ package org.springframework.boot.actuate.endpoint.web; import java.io.IOException; import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; import javax.servlet.GenericServlet; import javax.servlet.Servlet; @@ -49,7 +47,6 @@ import static org.mockito.Mockito.verify; * Tests for {@link ServletEndpointRegistrar}. * * @author Phillip Webb - * @author Madhura Bhave */ public class ServletEndpointRegistrarTests { @@ -76,14 +73,14 @@ public class ServletEndpointRegistrarTests { public void createWhenServletEndpointsIsNullShouldThrowException() { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("ServletEndpoints must not be null"); - new ServletEndpointRegistrar((String) null, null); + new ServletEndpointRegistrar(null, null); } @Test public void onStartupShouldRegisterServlets() throws Exception { ExposableServletEndpoint endpoint = mockEndpoint( new EndpointServlet(TestServlet.class)); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar((String) null, + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(null, Collections.singleton(endpoint)); registrar.onStartup(this.servletContext); verify(this.servletContext).addServlet(eq("test-actuator-endpoint"), @@ -105,64 +102,6 @@ public class ServletEndpointRegistrarTests { verify(this.dynamic).addMapping("/actuator/test/*"); } - @Test - public void onStartupWhenHasMultipleBasePathsShouldIncludeAllBasePaths() - throws Exception { - ExposableServletEndpoint endpoint = mockEndpoint( - new EndpointServlet(TestServlet.class)); - Set basePaths = new LinkedHashSet<>(); - basePaths.add("/actuator"); - basePaths.add("/admin"); - basePaths.add("/application"); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(basePaths, - Collections.singleton(endpoint)); - registrar.onStartup(this.servletContext); - verify(this.servletContext).addServlet(eq("test-actuator-endpoint"), - this.servlet.capture()); - assertThat(this.servlet.getValue()).isInstanceOf(TestServlet.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(this.dynamic).addMapping(captor.capture()); - assertThat(captor.getAllValues()).containsExactlyInAnyOrder("/application/test/*", - "/admin/test/*", "/actuator/test/*"); - } - - @Test - public void onStartupWhenHasEmptyBasePathsShouldIncludeRoot() throws Exception { - ExposableServletEndpoint endpoint = mockEndpoint( - new EndpointServlet(TestServlet.class)); - Set basePaths = Collections.emptySet(); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(basePaths, - Collections.singleton(endpoint)); - registrar.onStartup(this.servletContext); - verify(this.dynamic).addMapping("/test/*"); - } - - @Test - public void onStartupWhenHasBasePathsHasNullValueShouldIncludeRoot() - throws Exception { - ExposableServletEndpoint endpoint = mockEndpoint( - new EndpointServlet(TestServlet.class)); - Set basePaths = new LinkedHashSet<>(); - basePaths.add(null); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(basePaths, - Collections.singleton(endpoint)); - registrar.onStartup(this.servletContext); - verify(this.dynamic).addMapping("/test/*"); - } - - @Test - public void onStartupWhenDuplicateValuesShouldIncludeDistinct() throws Exception { - ExposableServletEndpoint endpoint = mockEndpoint( - new EndpointServlet(TestServlet.class)); - Set basePaths = new LinkedHashSet<>(); - basePaths.add(""); - basePaths.add(null); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(basePaths, - Collections.singleton(endpoint)); - registrar.onStartup(this.servletContext); - verify(this.dynamic).addMapping("/test/*"); - } - @Test public void onStartupWhenHasInitParametersShouldRegisterInitParameters() throws Exception { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java index 7e3d3d3f2d..8d473fcbc6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java @@ -27,7 +27,7 @@ import java.util.stream.Stream; import javax.servlet.http.HttpServletRequest; import org.springframework.boot.autoconfigure.security.StaticResourceLocation; -import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; @@ -96,14 +96,14 @@ public final class StaticResourceRequest { * Locations}. */ public static final class StaticResourceRequestMatcher - extends ApplicationContextRequestMatcher { + extends ApplicationContextRequestMatcher { private final Set locations; private volatile RequestMatcher delegate; private StaticResourceRequestMatcher(Set locations) { - super(ServerProperties.class); + super(DispatcherServletPath.class); this.locations = locations; } @@ -134,25 +134,26 @@ public final class StaticResourceRequest { } @Override - protected void initialized(Supplier serverProperties) { + protected void initialized( + Supplier dispatcherServletPath) { this.delegate = new OrRequestMatcher( - getDelegateMatchers(serverProperties.get())); + getDelegateMatchers(dispatcherServletPath.get())); } private List getDelegateMatchers( - ServerProperties serverProperties) { - return getPatterns(serverProperties).map(AntPathRequestMatcher::new) + DispatcherServletPath dispatcherServletPath) { + return getPatterns(dispatcherServletPath).map(AntPathRequestMatcher::new) .collect(Collectors.toList()); } - private Stream getPatterns(ServerProperties serverProperties) { + private Stream getPatterns(DispatcherServletPath dispatcherServletPath) { return this.locations.stream().flatMap(StaticResourceLocation::getPatterns) - .map(serverProperties.getServlet()::getPath); + .map(dispatcherServletPath::getRelativePath); } @Override protected boolean matches(HttpServletRequest request, - Supplier context) { + Supplier context) { return this.delegate.matches(request); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 2ef354cfa6..becb1d2eea 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -269,6 +269,13 @@ public class ServerProperties { return this.session; } + /** + * Return the mapping used to map a servlet to the path. + * @return the servlet mapping + * @deprecated since 2.0.4 in favor of + * {@link org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath#getServletUrlMapping} + */ + @Deprecated public String getServletMapping() { if (this.path.equals("") || this.path.equals("/")) { return "/"; @@ -282,6 +289,14 @@ public class ServerProperties { return this.path + "/*"; } + /** + * Return a path relative to the servlet prefix. + * @param path the path to make relative + * @return the relative path + * @deprecated since 2.0.4 in favor of + * {@link org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath#getRelativePath(String)} + */ + @Deprecated public String getPath(String path) { String prefix = getServletPrefix(); if (!path.startsWith("/")) { @@ -290,6 +305,13 @@ public class ServerProperties { return prefix + path; } + /** + * Return the servlet prefix. + * @return the servlet prefix + * @deprecated since 2.0.4 in favor of + * {@link org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath#getPrefix()} + */ + @Deprecated public String getServletPrefix() { String result = this.path; int index = result.indexOf('*'); @@ -302,6 +324,14 @@ public class ServerProperties { return result; } + /** + * Create a array of relative paths from the given source. + * @param paths the source paths + * @return the relative paths + * @deprecated since 2.0.4 in favor of + * {@link org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath#getRelativePath(String)} + */ + @Deprecated public String[] getPathsArray(Collection paths) { String[] result = new String[paths.size()]; int i = 0; @@ -311,6 +341,14 @@ public class ServerProperties { return result; } + /** + * Create a array of relative paths from the given source. + * @param paths the source paths + * @return the relative paths + * @deprecated since 2.0.4 in favor of + * {@link org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath#getRelativePath(String)} + */ + @Deprecated public String[] getPathsArray(String[] paths) { String[] result = new String[paths.length]; int i = 0; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java index 321c6d311d..88c878cd12 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.web.servlet; import java.util.Arrays; -import java.util.Collections; import java.util.List; import javax.servlet.MultipartConfigElement; @@ -34,7 +33,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; @@ -139,11 +137,10 @@ public class DispatcherServletAutoConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) - public ServletRegistrationBean dispatcherServletRegistration( + public DispatcherServletRegistrationBean dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { - ServletRegistrationBean registration = new ServletRegistrationBean<>( - dispatcherServlet, - this.serverProperties.getServlet().getServletMapping()); + DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean( + dispatcherServlet, this.serverProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); @@ -153,15 +150,6 @@ public class DispatcherServletAutoConfiguration { return registration; } - @Bean - @ConditionalOnMissingBean(DispatcherServletPathProvider.class) - @ConditionalOnSingleCandidate(DispatcherServlet.class) - public DispatcherServletPathProvider dispatcherServletPathProvider() { - return () -> Collections.singleton( - DispatcherServletRegistrationConfiguration.this.serverProperties - .getServlet().getPath()); - } - } @Order(Ordered.LOWEST_PRECEDENCE - 10) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.java new file mode 100644 index 0000000000..aede028d0b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.java @@ -0,0 +1,89 @@ +/* + * 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.servlet; + +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Interface that can be used by auto-configurations that need path details for the + * {@link DispatcherServletAutoConfiguration#DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME + * default} {@link DispatcherServlet}. + * + * @author Madhura Bhave + * @author Stephane Nicoll + * @since 2.0.4 + */ +@FunctionalInterface +public interface DispatcherServletPath { + + /** + * Returns the configured path of the dispatcher servlet. + * @return the configured path + */ + String getPath(); + + /** + * Return a form of the given path that's relative to the dispatcher servlet path. + * @param path the path to make relative + * @return the relative path + */ + default String getRelativePath(String path) { + String prefix = getPrefix(); + if (!path.startsWith("/")) { + path = "/" + path; + } + return prefix + path; + } + + /** + * Return a cleaned up version of the path that can be used as a prefix for URLs. The + * resulting path will have path will not have a trailing slash. + * @return the prefix + * @see #getRelativePath(String) + */ + default String getPrefix() { + String result = getPath(); + int index = result.indexOf('*'); + if (index != -1) { + result = result.substring(0, index); + } + if (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + /** + * Return a URL mapping pattern that can be used with a + * {@link ServletRegistrationBean} to map the dispatcher servlet. + * @return the path as a servlet URL mapping + */ + default String getServletUrlMapping() { + if (getPath().equals("") || getPath().equals("/")) { + return "/"; + } + if (getPath().contains("*")) { + return getPath(); + } + if (getPath().endsWith("/")) { + return getPath() + "*"; + } + return getPath() + "/*"; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathProvider.java index d0cb308d6b..76b6fa97bb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathProvider.java @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.web.servlet; -import java.util.Set; - import org.springframework.web.servlet.DispatcherServlet; /** @@ -26,10 +24,13 @@ import org.springframework.web.servlet.DispatcherServlet; * * @author Madhura Bhave * @since 2.0.2 + * @deprecated since 2.0.4 in favor of {@link DispatcherServletPath} and + * {@link DispatcherServletRegistrationBean} */ +@Deprecated @FunctionalInterface public interface DispatcherServletPathProvider { - Set getServletPaths(); + String getServletPath(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBean.java new file mode 100644 index 0000000000..541f899b83 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBean.java @@ -0,0 +1,67 @@ +/* + * 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.servlet; + +import java.util.Collection; + +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.util.Assert; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * {@link ServletRegistrationBean} for the auto-configured {@link DispatcherServlet}. Both + * registeres the servlet and exposes {@link DispatcherServletPath} information. + * + * @author Phillip Webb + * @since 2.0.4 + */ +public class DispatcherServletRegistrationBean extends + ServletRegistrationBean implements DispatcherServletPath { + + private final String path; + + /** + * Create a new {@link DispatcherServletRegistrationBean} instance for the given + * servlet and path. + * @param servlet the dispatcher servlet + * @param path the dispatcher servlet path + */ + public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) { + super(servlet); + Assert.notNull(path, "Path must not be null"); + this.path = path; + super.addUrlMappings(getServletUrlMapping()); + } + + @Override + public String getPath() { + return this.path; + } + + @Override + public void setUrlMappings(Collection urlMappings) { + throw new UnsupportedOperationException( + "URL Mapping cannot be changed on a DispatcherServlet registration"); + } + + @Override + public void addUrlMappings(String... urlMappings) { + throw new UnsupportedOperationException( + "URL Mapping cannot be changed on a DispatcherServlet registration"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java index 25c81e2e9f..25431fd514 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java @@ -49,6 +49,7 @@ import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvi import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.server.ErrorPage; @@ -94,11 +95,15 @@ public class ErrorMvcAutoConfiguration { private final ServerProperties serverProperties; + private final DispatcherServletPath dispatcherServletPath; + private final List errorViewResolvers; public ErrorMvcAutoConfiguration(ServerProperties serverProperties, + DispatcherServletPath dispatcherServletPath, ObjectProvider> errorViewResolversProvider) { this.serverProperties = serverProperties; + this.dispatcherServletPath = dispatcherServletPath; this.errorViewResolvers = errorViewResolversProvider.getIfAvailable(); } @@ -118,7 +123,7 @@ public class ErrorMvcAutoConfiguration { @Bean public ErrorPageCustomizer errorPageCustomizer() { - return new ErrorPageCustomizer(this.serverProperties); + return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath); } @Bean @@ -327,15 +332,18 @@ public class ErrorMvcAutoConfiguration { private final ServerProperties properties; - protected ErrorPageCustomizer(ServerProperties properties) { + private final DispatcherServletPath dispatcherServletPath; + + protected ErrorPageCustomizer(ServerProperties properties, + DispatcherServletPath dispatcherServletPath) { this.properties = properties; + this.dispatcherServletPath = dispatcherServletPath; } @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { - ErrorPage errorPage = new ErrorPage( - this.properties.getServlet().getServletPrefix() - + this.properties.getError().getPath()); + ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath + .getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java index 01ef099e31..80916df613 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java @@ -25,6 +25,7 @@ import org.junit.rules.ExpectedException; import org.springframework.boot.autoconfigure.security.StaticResourceLocation; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -96,15 +97,20 @@ public class StaticResourceRequestTests { } private RequestMatcherAssert assertMatcher(RequestMatcher matcher) { + DispatcherServletPath dispatcherServletPath = () -> ""; StaticWebApplicationContext context = new StaticWebApplicationContext(); context.registerBean(ServerProperties.class); + context.registerBean(DispatcherServletPath.class, () -> dispatcherServletPath); return assertThat(new RequestMatcherAssert(context, matcher)); } private RequestMatcherAssert assertMatcher(RequestMatcher matcher, ServerProperties serverProperties) { + DispatcherServletPath dispatcherServletPath = () -> serverProperties.getServlet() + .getPath(); StaticWebApplicationContext context = new StaticWebApplicationContext(); context.registerBean(ServerProperties.class, () -> serverProperties); + context.registerBean(DispatcherServletPath.class, () -> dispatcherServletPath); return assertThat(new RequestMatcherAssert(context, matcher)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index c5ce34c9c5..ddebfab559 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -79,6 +79,7 @@ public class ServerPropertiesTests { } @Test + @Deprecated public void testServletPathAsMapping() { bind("server.servlet.path", "/foo/*"); assertThat(this.properties.getServlet().getServletMapping()).isEqualTo("/foo/*"); @@ -86,6 +87,7 @@ public class ServerPropertiesTests { } @Test + @Deprecated public void testServletPathAsPrefix() { bind("server.servlet.path", "/foo"); assertThat(this.properties.getServlet().getServletMapping()).isEqualTo("/foo/*"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java index 1655fc3eb1..ce0bb8d910 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java @@ -66,8 +66,7 @@ public class DispatcherServletAutoConfigurationTests { .run((context) -> { assertThat(context).doesNotHaveBean(ServletRegistrationBean.class); assertThat(context).doesNotHaveBean(DispatcherServlet.class); - assertThat(context) - .doesNotHaveBean(DispatcherServletPathProvider.class); + assertThat(context).doesNotHaveBean(DispatcherServletPath.class); }); } @@ -77,7 +76,7 @@ public class DispatcherServletAutoConfigurationTests { public void registrationOverrideWithDispatcherServletWrongName() { this.contextRunner .withUserConfiguration(CustomDispatcherServletDifferentName.class, - CustomDispatcherServletPathProvider.class) + CustomDispatcherServletPath.class) .run((context) -> { ServletRegistrationBean registration = context .getBean(ServletRegistrationBean.class); @@ -91,7 +90,7 @@ public class DispatcherServletAutoConfigurationTests { @Test public void registrationOverrideWithAutowiredServlet() { this.contextRunner.withUserConfiguration(CustomAutowiredRegistration.class, - CustomDispatcherServletPathProvider.class).run((context) -> { + CustomDispatcherServletPath.class).run((context) -> { ServletRegistrationBean registration = context .getBean(ServletRegistrationBean.class); assertThat(registration.getUrlMappings()).containsExactly("/foo"); @@ -111,43 +110,35 @@ public class DispatcherServletAutoConfigurationTests { assertThat(registration.getUrlMappings()) .containsExactly("/spring/*"); assertThat(registration.getMultipartConfig()).isNull(); - assertThat(context.getBean(DispatcherServletPathProvider.class) - .getServletPaths()).containsExactly("/spring"); + assertThat(context.getBean(DispatcherServletPath.class).getPath()) + .isEqualTo("/spring"); }); } @Test - public void pathProviderNotCreatedWhenMultipleDispatcherServletsPresent() { - this.contextRunner - .withUserConfiguration(CustomDispatcherServletDifferentName.class) - .run((context) -> assertThat(context) - .doesNotHaveBean(DispatcherServletPathProvider.class)); - } - - @Test - public void pathProviderWhenCustomDispatcherServletSameNameShouldReturnConfiguredServletPath() { + public void dispatcherServletPathWhenCustomDispatcherServletSameNameShouldReturnConfiguredServletPath() { this.contextRunner.withUserConfiguration(CustomDispatcherServletSameName.class) .withPropertyValues("server.servlet.path:/spring") - .run((context) -> assertThat(context - .getBean(DispatcherServletPathProvider.class).getServletPaths()) - .containsExactly("/spring")); + .run((context) -> assertThat( + context.getBean(DispatcherServletPath.class).getPath()) + .isEqualTo("/spring")); } @Test - public void pathProviderNotCreatedWhenDefaultDispatcherServletNotAvailable() { + public void dispatcherServletPathNotCreatedWhenDefaultDispatcherServletNotAvailable() { this.contextRunner .withUserConfiguration(CustomDispatcherServletDifferentName.class, NonServletConfiguration.class) .run((context) -> assertThat(context) - .doesNotHaveBean(DispatcherServletPathProvider.class)); + .doesNotHaveBean(DispatcherServletPath.class)); } @Test - public void pathProviderNotCreatedWhenCustomRegistrationBeanPresent() { + public void dispatcherServletPathNotCreatedWhenCustomRegistrationBeanPresent() { this.contextRunner .withUserConfiguration(CustomDispatcherServletRegistration.class) .run((context) -> assertThat(context) - .doesNotHaveBean(DispatcherServletPathProvider.class)); + .doesNotHaveBean(DispatcherServletPath.class)); } @Test @@ -237,11 +228,11 @@ public class DispatcherServletAutoConfigurationTests { } @Configuration - protected static class CustomDispatcherServletPathProvider { + protected static class CustomDispatcherServletPath { @Bean - public DispatcherServletPathProvider dispatcherServletPathProvider() { - return mock(DispatcherServletPathProvider.class); + public DispatcherServletPath dispatcherServletPath() { + return mock(DispatcherServletPath.class); } } @@ -259,8 +250,8 @@ public class DispatcherServletAutoConfigurationTests { } @Bean - public DispatcherServletPathProvider dispatcherServletPathProvider() { - return mock(DispatcherServletPathProvider.class); + public DispatcherServletPath dispatcherServletPath() { + return mock(DispatcherServletPath.class); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathTests.java new file mode 100644 index 0000000000..663d89d1b9 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathTests.java @@ -0,0 +1,88 @@ +/* + * 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.servlet; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DispatcherServletPath}. + * + * @author Phillip Webb + */ +public class DispatcherServletPathTests { + + @Test + public void getRelativePathReturnsRelativePath() { + assertThat(((DispatcherServletPath) () -> "spring").getRelativePath("boot")) + .isEqualTo("spring/boot"); + assertThat(((DispatcherServletPath) () -> "spring/").getRelativePath("boot")) + .isEqualTo("spring/boot"); + assertThat(((DispatcherServletPath) () -> "spring").getRelativePath("/boot")) + .isEqualTo("spring/boot"); + } + + @Test + public void getPrefixWhenHasSimplePathReturnPath() { + assertThat(((DispatcherServletPath) () -> "spring").getPrefix()) + .isEqualTo("spring"); + } + + @Test + public void getPrefixWhenHasPatternRemovesPattern() { + assertThat(((DispatcherServletPath) () -> "spring/*.do").getPrefix()) + .isEqualTo("spring"); + } + + @Test + public void getPathWhenPathEndsWithSlashRemovesSlash() { + assertThat(((DispatcherServletPath) () -> "spring/").getPrefix()) + .isEqualTo("spring"); + } + + @Test + public void getServletUrlMappingWhenPathIsEmptyReturnsSlash() { + assertThat(((DispatcherServletPath) () -> "").getServletUrlMapping()) + .isEqualTo("/"); + } + + @Test + public void getServletUrlMappingWhenPathIsSlashReturnsSlash() { + assertThat(((DispatcherServletPath) () -> "/").getServletUrlMapping()) + .isEqualTo("/"); + } + + @Test + public void getServletUrlMappingWhenPathContainsStarReturnsPath() { + assertThat(((DispatcherServletPath) () -> "spring/*.do").getServletUrlMapping()) + .isEqualTo("spring/*.do"); + } + + @Test + public void getServletUrlMappingWhenHasPathNotEndingSlashReturnsSlashStarPattern() { + assertThat(((DispatcherServletPath) () -> "spring/boot").getServletUrlMapping()) + .isEqualTo("spring/boot/*"); + } + + @Test + public void getServletUrlMappingWhenHasPathEndingWithSlashReturnsSlashStarPattern() { + assertThat(((DispatcherServletPath) () -> "spring/boot/").getServletUrlMapping()) + .isEqualTo("spring/boot/*"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBeanTests.java new file mode 100644 index 0000000000..5f0e70278a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBeanTests.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.autoconfigure.web.servlet; + +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.web.servlet.DispatcherServlet; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DispatcherServletRegistrationBean}. + * + * @author Phillip Webb + */ +public class DispatcherServletRegistrationBeanTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void createWhenPathIsNullThrowsException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Path must not be null"); + new DispatcherServletRegistrationBean(new DispatcherServlet(), null); + } + + @Test + public void getPathReturnsPath() { + DispatcherServletRegistrationBean bean = new DispatcherServletRegistrationBean( + new DispatcherServlet(), "/test"); + assertThat(bean.getPath()).isEqualTo("/test"); + } + + @Test + public void getUrlMappingsReturnsSinglePathMappedPattern() { + DispatcherServletRegistrationBean bean = new DispatcherServletRegistrationBean( + new DispatcherServlet(), "/test"); + assertThat(bean.getUrlMappings()).containsOnly("/test/*"); + } + + @Test + public void setUrlMappingsCannotBeCalled() { + DispatcherServletRegistrationBean bean = new DispatcherServletRegistrationBean( + new DispatcherServlet(), "/test"); + this.thrown.expect(UnsupportedOperationException.class); + bean.setUrlMappings(Collections.emptyList()); + } + + @Test + public void addUrlMappingsCannotBeCalled() { + DispatcherServletRegistrationBean bean = new DispatcherServletRegistrationBean( + new DispatcherServlet(), "/test"); + this.thrown.expect(UnsupportedOperationException.class); + bean.addUrlMappings("/test"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java index 21dc96fdaa..a266abd972 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java @@ -20,6 +20,7 @@ import org.junit.Rule; import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.rule.OutputCapture; import org.springframework.boot.web.servlet.error.ErrorAttributes; @@ -39,7 +40,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class ErrorMvcAutoConfigurationTests { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ErrorMvcAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(DispatcherServletAutoConfiguration.class, + ErrorMvcAutoConfiguration.class)); @Rule public OutputCapture outputCapture = new OutputCapture(); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java index 28922d6a1b..4fe86a2c6a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.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. @@ -22,6 +22,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -48,19 +50,28 @@ import org.springframework.web.servlet.DispatcherServlet; @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @AutoConfigureAfter(WebMvcAutoConfiguration.class) -@EnableConfigurationProperties(WebMvcProperties.class) +@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class }) public class MockMvcAutoConfiguration { private final WebApplicationContext context; + private final ServerProperties serverProperties; + private final WebMvcProperties webMvcProperties; MockMvcAutoConfiguration(WebApplicationContext context, - WebMvcProperties webMvcProperties) { + ServerProperties serverProperties, WebMvcProperties webMvcProperties) { this.context = context; + this.serverProperties = serverProperties; this.webMvcProperties = webMvcProperties; } + @Bean + @ConditionalOnMissingBean + public DispatcherServletPath dispatcherServletPath() { + return () -> this.serverProperties.getServlet().getPath(); + } + @Bean @ConditionalOnMissingBean(MockMvcBuilder.class) public DefaultMockMvcBuilder mockMvcBuilder(