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 3fdb9817e76..c62b3eef332 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 @@ -29,7 +29,6 @@ import java.util.Map.Entry; import java.util.Optional; import javax.servlet.Servlet; -import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -74,11 +73,9 @@ import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.DateFormatter; import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; @@ -108,11 +105,9 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; -import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver; -import org.springframework.web.servlet.mvc.ParameterizableViewController; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -611,40 +606,6 @@ public class WebMvcAutoConfiguration { } - static final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { - - private static final Log logger = LogFactory - .getLog(WelcomePageHandlerMapping.class); - - private WelcomePageHandlerMapping(Optional welcomePage, - String staticPathPattern) { - if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { - logger.info("Adding welcome page: " + welcomePage); - ParameterizableViewController controller = new ParameterizableViewController(); - controller.setViewName("forward:index.html"); - setRootHandler(controller); - setOrder(0); - } - } - - @Override - public Object getHandlerInternal(HttpServletRequest request) throws Exception { - for (MediaType mediaType : getAcceptedMediaTypes(request)) { - if (mediaType.includes(MediaType.TEXT_HTML)) { - return super.getHandlerInternal(request); - } - } - return null; - } - - private List getAcceptedMediaTypes(HttpServletRequest request) { - String acceptHeader = request.getHeader(HttpHeaders.ACCEPT); - return MediaType.parseMediaTypes( - StringUtils.hasText(acceptHeader) ? acceptHeader : "*/*"); - } - - } - /** * Decorator to make {@link PathExtensionContentNegotiationStrategy} optional * depending on a request attribute. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java new file mode 100644 index 00000000000..c1887e4f7e3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2017 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.List; +import java.util.Optional; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; +import org.springframework.web.servlet.mvc.ParameterizableViewController; + +/** + * An {@link AbstractUrlHandlerMapping} for an application's welcome page. Supports both + * static and templated files. If both a static and templated index page is available, the + * static page is preferred. + * + * @author Andy Wilkinson + */ +final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { + + private static final Log logger = LogFactory.getLog(WelcomePageHandlerMapping.class); + + WelcomePageHandlerMapping(Optional welcomePage, String staticPathPattern) { + if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { + logger.info("Adding welcome page: " + welcomePage.get()); + ParameterizableViewController controller = new ParameterizableViewController(); + controller.setViewName("forward:index.html"); + setRootHandler(controller); + setOrder(0); + } + } + + @Override + public Object getHandlerInternal(HttpServletRequest request) throws Exception { + for (MediaType mediaType : getAcceptedMediaTypes(request)) { + if (mediaType.includes(MediaType.TEXT_HTML)) { + return super.getHandlerInternal(request); + } + } + return null; + } + + private List getAcceptedMediaTypes(HttpServletRequest request) { + String acceptHeader = request.getHeader(HttpHeaders.ACCEPT); + return MediaType.parseMediaTypes( + StringUtils.hasText(acceptHeader) ? acceptHeader : "*/*"); + } + +} 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 35129489cb6..54eb65f7124 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 @@ -39,7 +39,6 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WelcomePageHandlerMapping; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -56,12 +55,9 @@ import org.springframework.core.io.Resource; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.StringUtils; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; @@ -101,9 +97,6 @@ import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcAutoConfiguration}. @@ -579,102 +572,17 @@ public class WebMvcAutoConfigurationTests { } @Test - public void welcomePageMappingProducesNotFoundResponseWhenThereIsNoWelcomePage() { - this.contextRunner - .withPropertyValues( - "spring.resources.static-locations:classpath:/no-welcome-page/") - .run((context) -> { - assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - MockMvcBuilders.webAppContextSetup(context).build() - .perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isNotFound()); - }); - } - - @Test - public void welcomePageRootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() { - this.contextRunner - .withPropertyValues( - "spring.resources.static-locations:classpath:/welcome-page/", - "spring.mvc.static-path-pattern:/foo/**") - .run((context) -> assertThat( - context.getBean(WelcomePageHandlerMapping.class).getRootHandler()) - .isNull()); - } - - @Test - public void welcomePageMappingHandlesRequestsThatAcceptTextHtml() { + public void welcomePageHandlerMappingIsAutoConfigured() { this.contextRunner .withPropertyValues( "spring.resources.static-locations:classpath:/welcome-page/") .run((context) -> { assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html")); - mockMvc.perform(get("/").accept("*/*")).andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html")); + assertThat(context.getBean(WelcomePageHandlerMapping.class) + .getRootHandler()).isNotNull(); }); } - @Test - public void welcomePageMappingDoesNotHandleRequestsThatDoNotAcceptTextHtml() { - this.contextRunner - .withPropertyValues( - "spring.resources.static-locations:classpath:/welcome-page/") - .run((context) -> { - assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); - }); - } - - @Test - public void welcomePageMappingHandlesRequestsWithNoAcceptHeader() { - this.contextRunner - .withPropertyValues( - "spring.resources.static-locations:classpath:/welcome-page/") - .run((context) -> { - assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/")).andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html")); - }); - } - - @Test - public void welcomePageMappingHandlesRequestsWithEmptyAcceptHeader() - throws Exception { - this.contextRunner - .withPropertyValues( - "spring.resources.static-locations:classpath:/welcome-page/") - .run((context) -> { - assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").header(HttpHeaders.ACCEPT, "")) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html")); - }); - } - - @Test - public void welcomePageMappingWorksWithNoTrailingSlashOnResourceLocation() - throws Exception { - this.contextRunner - .withPropertyValues( - "spring.resources.static-locations:classpath:/welcome-page") - .run((context) -> { - assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html")); - }); - - } - @Test public void validatorWhenNoValidatorShouldUseDefault() { this.contextRunner.run((context) -> { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java new file mode 100644 index 00000000000..dff3a01ba44 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2012-2017 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.Optional; + +import org.junit.Test; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link WelcomePageHandlerMapping}. + * + * @author Andy Wilkinson + */ +public class WelcomePageHandlerMappingTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withUserConfiguration(HandlerMappingConfiguration.class).withConfiguration( + AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class)); + + @Test + public void handlesRequestForStaticPageThatAcceptsTextHtml() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("index.html"))); + } + + @Test + public void handlesRequestForStaticPageThatAcceptsAll() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").accept("*/*")).andExpect(status().isOk()) + .andExpect(forwardedUrl("index.html"))); + } + + @Test + public void doesNotHandleRequestThatDoesNotAcceptTextHtml() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound())); + } + + @Test + public void handlesRequestWithNoAcceptHeader() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/")).andExpect(status().isOk()) + .andExpect(forwardedUrl("index.html"))); + } + + @Test + public void handlesRequestWithEmptyAcceptHeader() throws Exception { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").header(HttpHeaders.ACCEPT, "")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("index.html"))); + + } + + @Test + public void rootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .withPropertyValues("static-path-pattern=/foo/**") + .run((context) -> assertThat( + context.getBean(WelcomePageHandlerMapping.class).getRootHandler()) + .isNull()); + } + + @Test + public void producesNotFoundResponseWhenThereIsNoWelcomePage() { + this.contextRunner.run((context) -> MockMvcBuilders.webAppContextSetup(context) + .build().perform(get("/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isNotFound())); + } + + @Configuration + static class HandlerMappingConfiguration { + + @Bean + public WelcomePageHandlerMapping handlerMapping( + ApplicationContext applicationContext, + ObjectProvider staticIndexPage, + @Value("${static-path-pattern:/**}") String staticPathPattern) { + return new WelcomePageHandlerMapping( + Optional.ofNullable(staticIndexPage.getIfAvailable()), + staticPathPattern); + } + + } + + @Configuration + static class StaticResourceConfiguration { + + @Bean + public Resource staticIndexPage() { + return new FileSystemResource("src/test/resources/welcome-page/index.html"); + } + + } + +}