Commit 2e1988f9 authored by Andy Wilkinson's avatar Andy Wilkinson

Rework welcome page so that it only handles reqs that accept text/html

Closes gh-6668
parent 1c294dd5
......@@ -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;
}
}
}
......@@ -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);
......
/*
* 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);
}
}
}
<html><body>Hello!</body></html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment