From 2e1988f97ec5db7d193f3e2bfa00643e548d4338 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 31 Aug 2016 17:39:43 +0100 Subject: [PATCH] Rework welcome page so that it only handles reqs that accept text/html Closes gh-6668 --- .../web/WebMvcAutoConfiguration.java | 48 ++++++-- .../web/WebMvcAutoConfigurationTests.java | 54 +++++++- .../web/WelcomePageMockMvcTests.java | 115 ------------------ .../src/test/resources/custom/index.html | 1 - .../test/resources/welcome-page/index.html | 0 5 files changed, 91 insertions(+), 127 deletions(-) delete mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WelcomePageMockMvcTests.java delete mode 100644 spring-boot-autoconfigure/src/test/resources/custom/index.html create mode 100644 spring-boot-autoconfigure/src/test/resources/welcome-page/index.html diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index f6516f31f3..338e97abb3 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Map.Entry; import javax.servlet.Servlet; +import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -57,6 +58,7 @@ import org.springframework.core.io.Resource; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.DateFormatter; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.validation.DefaultMessageCodesResolver; @@ -79,13 +81,14 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceChainRegistration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 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; @@ -288,6 +291,12 @@ public class WebMvcAutoConfiguration { } } + @Bean + public WelcomePageHandlerMapping welcomePageHandlerMapping( + ResourceProperties resourceProperties) { + return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage()); + } + private void customizeResourceHandlerRegistration( ResourceHandlerRegistration registration) { if (this.resourceHandlerRegistrationCustomizer != null) { @@ -296,15 +305,6 @@ public class WebMvcAutoConfiguration { } - @Override - public void addViewControllers(ViewControllerRegistry registry) { - Resource page = this.resourceProperties.getWelcomePage(); - if (page != null) { - logger.info("Adding welcome page: " + page); - registry.addViewController("/").setViewName("forward:index.html"); - } - } - @Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) @@ -498,4 +498,32 @@ public class WebMvcAutoConfiguration { } + static final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { + + private static final Log logger = LogFactory + .getLog(WelcomePageHandlerMapping.class); + + private WelcomePageHandlerMapping(Resource welcomePage) { + if (welcomePage != null) { + 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 : MediaType + .parseMediaTypes(request.getHeader(HttpHeaders.ACCEPT))) { + if (mediaType.includes(MediaType.TEXT_HTML)) { + return super.getHandlerInternal(request); + } + } + return null; + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index 4835c995ce..963581a975 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -39,6 +39,7 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WelcomePageHandlerMapping; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; @@ -52,8 +53,11 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; 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.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.accept.ContentNegotiationManager; @@ -90,6 +94,9 @@ import org.springframework.web.servlet.view.AbstractView; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; 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 WebMvcAutoConfiguration}. @@ -132,7 +139,7 @@ public class WebMvcAutoConfigurationTests { public void handlerMappingsCreated() throws Exception { load(); assertThat(this.context.getBeanNamesForType(HandlerMapping.class).length) - .isEqualTo(6); + .isEqualTo(7); } @Test @@ -540,6 +547,51 @@ public class WebMvcAutoConfigurationTests { testLogResolvedExceptionCustomization(true); } + @Test + public void welcomePageMappingProducesNotFoundResponseWhenThereIsNoWelcomePage() + throws Exception { + load("spring.resources.static-locations:classpath:/no-welcome-page/"); + assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) + .hasSize(1); + MockMvcBuilders.webAppContextSetup(this.context).build() + .perform(get("/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isNotFound()); + } + + @Test + public void welcomePageMappingHandlesRequestsThatAcceptTextHtml() throws Exception { + load("spring.resources.static-locations:classpath:/welcome-page/"); + assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) + .hasSize(1); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.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")); + } + + @Test + public void welcomePageMappingOnlyHandlesRequestsThatAcceptTextHtml() + throws Exception { + load("spring.resources.static-locations:classpath:/welcome-page/"); + assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) + .hasSize(1); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + public void welcomePageMappingWorksWithNoTrailingSlashOnResourceLocation() + throws Exception { + load("spring.resources.static-locations:classpath:/welcome-page"); + assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) + .hasSize(1); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isOk()) + .andExpect(forwardedUrl("index.html")); + } + private void testLogResolvedExceptionCustomization(final boolean expected) { HandlerExceptionResolver exceptionResolver = this.context .getBean(HandlerExceptionResolver.class); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WelcomePageMockMvcTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WelcomePageMockMvcTests.java deleted file mode 100644 index ef62e325ff..0000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WelcomePageMockMvcTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2012-2016 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 java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.ConfigurableWebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for welcome page using {@link MockMvc} and {@link SpringRunner}. - * - * @author Dave Syer - */ -public class WelcomePageMockMvcTests { - - private ConfigurableWebApplicationContext wac; - - private MockMvc mockMvc; - - @After - public void close() { - if (this.wac != null) { - this.wac.close(); - } - } - - @Test - public void homePageNotFound() throws Exception { - this.wac = (ConfigurableWebApplicationContext) new SpringApplicationBuilder( - TestConfiguration.class).run(); - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); - this.mockMvc.perform(get("/")).andExpect(status().isNotFound()).andReturn(); - } - - @Test - public void homePageCustomLocation() throws Exception { - this.wac = (ConfigurableWebApplicationContext) new SpringApplicationBuilder( - TestConfiguration.class) - .properties("spring.resources.staticLocations:classpath:/custom/") - .run(); - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); - this.mockMvc.perform(get("/")).andExpect(status().isOk()).andReturn(); - } - - @Test - public void homePageCustomLocationNoTrailingSlash() throws Exception { - this.wac = (ConfigurableWebApplicationContext) new SpringApplicationBuilder( - TestConfiguration.class) - .properties("spring.resources.staticLocations:classpath:/custom") - .run(); - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); - this.mockMvc.perform(get("/")).andExpect(status().isOk()).andReturn(); - } - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @Import({ ServerPropertiesAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) - protected @interface MinimalWebConfiguration { - } - - @Configuration - @MinimalWebConfiguration - public static class TestConfiguration { - - @Bean - public MockEmbeddedServletContainerFactory embeddedServletContainerFactory() { - return new MockEmbeddedServletContainerFactory(); - } - - // For manual testing - public static void main(String[] args) { - SpringApplication.run(TestConfiguration.class, args); - } - - } - -} diff --git a/spring-boot-autoconfigure/src/test/resources/custom/index.html b/spring-boot-autoconfigure/src/test/resources/custom/index.html deleted file mode 100644 index 5124d86069..0000000000 --- a/spring-boot-autoconfigure/src/test/resources/custom/index.html +++ /dev/null @@ -1 +0,0 @@ -Hello! \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/welcome-page/index.html b/spring-boot-autoconfigure/src/test/resources/welcome-page/index.html new file mode 100644 index 0000000000..e69de29bb2