diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java index c8b3cdd570..4d53fb6828 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java @@ -86,7 +86,7 @@ public class DispatcherServletAutoConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) public ServletRegistrationBean dispatcherServletRegistration() { ServletRegistrationBean registration = new ServletRegistrationBean( - dispatcherServlet(), this.server.getServletPath()); + dispatcherServlet(), this.server.getServletMapping()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java index eecdc646e5..33f137a224 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java @@ -24,6 +24,7 @@ import javax.servlet.Servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -67,11 +68,15 @@ import org.springframework.web.servlet.view.BeanNameViewResolver; // Ensure this loads before the main WebMvcAutoConfiguration so that the error View is // available @AutoConfigureBefore(WebMvcAutoConfiguration.class) +@Configuration public class ErrorMvcAutoConfiguration implements EmbeddedServletContainerCustomizer { @Value("${error.path:/error}") private String errorPath = "/error"; + @Autowired + private ServerProperties properties; + @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { @@ -86,7 +91,8 @@ public class ErrorMvcAutoConfiguration implements EmbeddedServletContainerCustom @Override public void customize(ConfigurableEmbeddedServletContainer container) { - container.addErrorPages(new ErrorPage(this.errorPath)); + container.addErrorPages(new ErrorPage(this.properties.getServletPrefix() + + this.errorPath)); } @Configuration 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 900486d0dd..9293b2065d 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 @@ -78,6 +78,33 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { return this.servletPath; } + public String getServletMapping() { + if (this.servletPath.equals("") || this.servletPath.equals("/")) { + return "/"; + } + if (this.servletPath.contains("*")) { + if (this.servletPath.endsWith("*")) { + return this.servletPath; + } + return this.servletPath; + } + if (this.servletPath.endsWith("/")) { + return this.servletPath + "*"; + } + return this.servletPath + "/*"; + } + + public String getServletPrefix() { + String result = this.servletPath; + if (result.contains("*")) { + result = result.substring(0, result.indexOf("*")); + } + if (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + public void setServletPath(String servletPath) { this.servletPath = servletPath; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfigurationTests.java index b7d36931a1..7080ad6bc6 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfigurationTests.java @@ -98,7 +98,7 @@ public class DispatcherServletAutoConfigurationTests { assertNotNull(this.context.getBean(DispatcherServlet.class)); ServletRegistrationBean registration = this.context .getBean(ServletRegistrationBean.class); - assertEquals("[/spring]", registration.getUrlMappings().toString()); + assertEquals("[/spring/*]", registration.getUrlMappings().toString()); assertNull(registration.getMultipartConfig()); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/RemappedErrorViewIntegrationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/RemappedErrorViewIntegrationTests.java new file mode 100644 index 0000000000..3223b45e11 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/RemappedErrorViewIntegrationTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2013 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; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.web.RemappedErrorViewIntegrationTests.TestConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.boot.context.embedded.ErrorPage; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Controller; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.client.RestTemplate; + +import static org.junit.Assert.assertTrue; + +/** + * @author Dave Syer + */ +@SpringApplicationConfiguration(classes = TestConfiguration.class) +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@IntegrationTest({ "server.servletPath:/spring/*", "server.port:0" }) +@DirtiesContext +public class RemappedErrorViewIntegrationTests { + + @Value("${local.server.port}") + private int port; + + private RestTemplate template = new TestRestTemplate(); + + @Test + public void directAccessToErrorPage() throws Exception { + String content = this.template.getForObject("http://localhost:" + this.port + + "/spring/error", String.class); + assertTrue("Wrong content: " + content, content.contains("error")); + assertTrue("Wrong content: " + content, content.contains("999")); + } + + @Test + public void forwardToErrorPage() throws Exception { + String content = this.template.getForObject("http://localhost:" + this.port + + "/spring/", String.class); + assertTrue("Wrong content: " + content, content.contains("error")); + assertTrue("Wrong content: " + content, content.contains("500")); + } + + @Configuration + @Import({ PropertyPlaceholderAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EmbeddedServletContainerAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, ErrorMvcAutoConfiguration.class }) + @Controller + public static class TestConfiguration implements EmbeddedServletContainerCustomizer { + + @RequestMapping("/") + public String home() { + throw new RuntimeException("Planned!"); + } + + @Override + public void customize(ConfigurableEmbeddedServletContainer container) { + container.addErrorPages(new ErrorPage("/spring/error")); + } + + // For manual testing + public static void main(String[] args) { + new SpringApplicationBuilder(TestConfiguration.class).properties( + "server.servletPath:spring/*").run(args); + } + + } + +}