From d7ff379fb8bb2c33319afeb45a343116b59e0ee2 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 15 May 2023 13:49:43 -0700 Subject: [PATCH] Return 406 status code if welcome page is not accepted Add `WelcomePageNotAcceptableHandlerMapping` which will return an HTTP 406 status if a suitable welcome page is found but cannot be accepted for the request. An additional mapper is used so that we don't need to change the order of the `WelcomePageHandlerMapping`. It's possible that users may have additional root handler mappings registered to run after the `WelcomePageHandlerMapping` and we still need to respect those. Fixes gh-35559 --- .../web/servlet/WebMvcAutoConfiguration.java | 53 +++++-- .../web/servlet/WelcomePage.java | 79 +++++++++++ .../servlet/WelcomePageHandlerMapping.java | 48 +++---- ...elcomePageNotAcceptableHandlerMapping.java | 58 ++++++++ .../servlet/WebMvcAutoConfigurationTests.java | 8 +- .../WelcomePageHandlerMappingTests.java | 3 +- .../servlet/WelcomePageIntegrationTests.java | 12 +- ...ePageNotAcceptableHandlerMappingTests.java | 132 ++++++++++++++++++ 8 files changed, 347 insertions(+), 46 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePage.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMapping.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java 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 a919f42862d8..11651188b8a6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -110,6 +110,7 @@ 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.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; @@ -453,12 +454,29 @@ public RequestMappingHandlerMapping requestMappingHandlerMapping( @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { - WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( - new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), - this.mvcProperties.getStaticPathPattern()); - welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); - welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); - return welcomePageHandlerMapping; + return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, + WelcomePageHandlerMapping::new); + } + + @Bean + public WelcomePageNotAcceptableHandlerMapping welcomePageNotAcceptableHandlerMapping( + ApplicationContext applicationContext, FormattingConversionService mvcConversionService, + ResourceUrlProvider mvcResourceUrlProvider) { + return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, + WelcomePageNotAcceptableHandlerMapping::new); + } + + private T createWelcomePageHandlerMapping( + ApplicationContext applicationContext, FormattingConversionService mvcConversionService, + ResourceUrlProvider mvcResourceUrlProvider, WelcomePageHandlerMappingFactory factory) { + TemplateAvailabilityProviders templateAvailabilityProviders = new TemplateAvailabilityProviders( + applicationContext); + String staticPathPattern = this.mvcProperties.getStaticPathPattern(); + T handlerMapping = factory.create(templateAvailabilityProviders, applicationContext, getIndexHtmlResource(), + staticPathPattern); + handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); + handlerMapping.setCorsConfigurations(getCorsConfigurations()); + return handlerMapping; } @Override @@ -493,25 +511,25 @@ public FlashMapManager flashMapManager() { return super.flashMapManager(); } - private Resource getWelcomePage() { + private Resource getIndexHtmlResource() { for (String location : this.resourceProperties.getStaticLocations()) { - Resource indexHtml = getIndexHtml(location); + Resource indexHtml = getIndexHtmlResource(location); if (indexHtml != null) { return indexHtml; } } ServletContext servletContext = getServletContext(); if (servletContext != null) { - return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION)); + return getIndexHtmlResource(new ServletContextResource(servletContext, SERVLET_LOCATION)); } return null; } - private Resource getIndexHtml(String location) { - return getIndexHtml(this.resourceLoader.getResource(location)); + private Resource getIndexHtmlResource(String location) { + return getIndexHtmlResource(this.resourceLoader.getResource(location)); } - private Resource getIndexHtml(Resource location) { + private Resource getIndexHtmlResource(Resource location) { try { Resource resource = location.createRelative("index.html"); if (resource.exists() && (resource.getURL() != null)) { @@ -626,6 +644,15 @@ ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCu } + @FunctionalInterface + interface WelcomePageHandlerMappingFactory { + + T create(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, + Resource indexHtmlResource, String staticPathPattern); + + } + + @FunctionalInterface interface ResourceHandlerRegistrationCustomizer { void customize(ResourceHandlerRegistration registration); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePage.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePage.java new file mode 100644 index 000000000000..1adad3cea9bf --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePage.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 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 + * + * https://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.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; + +/** + * Details for a welcome page resolved from a resource or a template. + * + * @author Phillip Webb + */ +final class WelcomePage { + + /** + * Value used for an unresolved welcome page. + */ + static final WelcomePage UNRESOLVED = new WelcomePage(null, false); + + private final String viewName; + + private final boolean templated; + + private WelcomePage(String viewName, boolean templated) { + this.viewName = viewName; + this.templated = templated; + } + + /** + * Return the view name of the welcome page. + * @return the view name + */ + String getViewName() { + return this.viewName; + } + + /** + * Return if the welcome page is from a template. + * @return if the welcome page is templated + */ + boolean isTemplated() { + return this.templated; + } + + /** + * Resolve the {@link WelcomePage} to use. + * @param templateAvailabilityProviders the template availability providers + * @param applicationContext the application context + * @param indexHtmlResource the index HTML resource to use or {@code null} + * @param staticPathPattern the static path pattern being used + * @return a resolved {@link WelcomePage} instance or {@link #UNRESOLVED} + */ + static WelcomePage resolve(TemplateAvailabilityProviders templateAvailabilityProviders, + ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { + if (indexHtmlResource != null && "/**".equals(staticPathPattern)) { + return new WelcomePage("forward:index.html", false); + } + if (templateAvailabilityProviders.getProvider("index", applicationContext) != null) { + return new WelcomePage("index", true); + } + return UNRESOLVED; + } + +} 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 index 2f33cbb3e9dc..8872c5f8a411 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; +import org.springframework.core.log.LogMessage; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.StringUtils; @@ -34,12 +35,13 @@ 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 are available, - * the static page is preferred. + * An {@link AbstractUrlHandlerMapping} for an application's HTML welcome page. Supports + * both static and templated files. If both a static and templated index page are + * available, the static page is preferred. * * @author Andy Wilkinson * @author Bruce Brouwer + * @see WelcomePageNotAcceptableHandlerMapping */ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { @@ -48,37 +50,31 @@ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { private static final List MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL); WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) { - if (welcomePage != null && "/**".equals(staticPathPattern)) { - logger.info("Adding welcome page: " + welcomePage); - setRootViewName("forward:index.html"); - } - else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { - logger.info("Adding welcome page template: index"); - setRootViewName("index"); - } - } - - private boolean welcomeTemplateExists(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext) { - return templateAvailabilityProviders.getProvider("index", applicationContext) != null; - } - - private void setRootViewName(String viewName) { - ParameterizableViewController controller = new ParameterizableViewController(); - controller.setViewName(viewName); - setRootHandler(controller); + ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { setOrder(2); + WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext, + indexHtmlResource, staticPathPattern); + if (welcomePage != WelcomePage.UNRESOLVED) { + logger.info(LogMessage.of(() -> (!welcomePage.isTemplated()) ? "Adding welcome page: " + indexHtmlResource + : "Adding welcome page template: index")); + ParameterizableViewController controller = new ParameterizableViewController(); + controller.setViewName(welcomePage.getViewName()); + setRootHandler(controller); + } } @Override public Object getHandlerInternal(HttpServletRequest request) throws Exception { + return (!isHtmlTextAccepted(request)) ? null : super.getHandlerInternal(request); + } + + private boolean isHtmlTextAccepted(HttpServletRequest request) { for (MediaType mediaType : getAcceptedMediaTypes(request)) { if (mediaType.includes(MediaType.TEXT_HTML)) { - return super.getHandlerInternal(request); + return true; } } - return null; + return false; } private List getAcceptedMediaTypes(HttpServletRequest request) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMapping.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMapping.java new file mode 100644 index 000000000000..7b7154df9dad --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMapping.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2023 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 + * + * https://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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; +import org.springframework.web.servlet.mvc.Controller; + +/** + * An {@link AbstractUrlHandlerMapping} for an application's welcome page that was + * ultimately not accepted. + * + * @author Phillip Webb + */ +class WelcomePageNotAcceptableHandlerMapping extends AbstractUrlHandlerMapping { + + WelcomePageNotAcceptableHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, + ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { + setOrder(LOWEST_PRECEDENCE - 10); // Before ResourceHandlerRegistry + WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext, + indexHtmlResource, staticPathPattern); + if (welcomePage != WelcomePage.UNRESOLVED) { + setRootHandler((Controller) this::handleRequest); + } + } + + private ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { + response.setStatus(HttpStatus.NOT_ACCEPTABLE.value()); + return null; + } + + @Override + protected Object getHandlerInternal(HttpServletRequest request) throws Exception { + return super.getHandlerInternal(request); + } + +} 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 26b958e68d9f..ea5a0665fdf6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -171,7 +171,7 @@ void handlerAdaptersCreated() { @Test void handlerMappingsCreated() { - this.contextRunner.run((context) -> assertThat(context).getBeans(HandlerMapping.class).hasSize(5)); + this.contextRunner.run((context) -> assertThat(context).getBeans(HandlerMapping.class).hasSize(6)); } @Test @@ -685,8 +685,8 @@ private ContextConsumer assertExceptionResolver void welcomePageHandlerMappingIsAutoConfigured(String prefix) { this.contextRunner.withPropertyValues(prefix + "static-locations:classpath:/welcome-page/").run((context) -> { assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - WelcomePageHandlerMapping bean = context.getBean(WelcomePageHandlerMapping.class); - assertThat(bean.getRootHandler()).isNotNull(); + assertThat(context.getBean(WelcomePageHandlerMapping.class).getRootHandler()).isNotNull(); + assertThat(context.getBean(WelcomePageNotAcceptableHandlerMapping.class).getRootHandler()).isNotNull(); }); } 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 index b907cdae2bec..e0bdca91beaf 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -105,7 +105,6 @@ void handlesRequestWithEmptyAcceptHeader() { .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() .perform(get("/").header(HttpHeaders.ACCEPT, "")).andExpect(status().isOk()) .andExpect(forwardedUrl("index.html"))); - } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java index 16c34202ebbf..4ec411346254 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -29,6 +29,7 @@ import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -56,6 +57,15 @@ void contentStrategyWithWelcomePage() throws Exception { .header("Accept", MediaType.ALL.toString()).build(); ResponseEntity content = this.template.exchange(entity, String.class); assertThat(content.getBody()).contains("/custom-"); + assertThat(content.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + void notAcceptableWelcomePage() throws Exception { + RequestEntity entity = RequestEntity.get(new URI("http://localhost:" + this.port + "/")) + .header("Accept", "spring/boot").build(); + ResponseEntity content = this.template.exchange(entity, String.class); + assertThat(content.getStatusCode()).isEqualTo(HttpStatus.NOT_ACCEPTABLE); } @Configuration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java new file mode 100644 index 000000000000..90a29617a790 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2012-2023 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 + * + * https://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.jupiter.api.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.autoconfigure.template.TemplateAvailabilityProviders; +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.Ordered; +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.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; + +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.status; + +/** + * Tests for {@link WelcomePageNotAcceptableHandlerMapping}. + * + * @author Phillip Webb + */ +class WelcomePageNotAcceptableHandlerMappingTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withUserConfiguration(HandlerMappingConfiguration.class) + .withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class)); + + @Test + void isOrderedAtLowPriorityButAboveResourceHandlerRegistry() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class).run((context) -> { + WelcomePageNotAcceptableHandlerMapping handler = context + .getBean(WelcomePageNotAcceptableHandlerMapping.class); + ResourceHandlerRegistry registry = new ResourceHandlerRegistry(context, null); + Integer resourceOrder = (Integer) ReflectionTestUtils.getField(registry, "order"); + assertThat(handler.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE - 10); + assertThat(handler.getOrder()).isLessThan(resourceOrder); + }); + } + + @Test + void handlesRequestForStaticPageThatAcceptsTextHtml() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isNotAcceptable())); + } + + @Test + void handlesRequestForStaticPagetThatDoesNotAcceptTextHtml() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").accept(MediaType.APPLICATION_JSON)).andExpect(status().isNotAcceptable())); + } + + @Test + void handlesRequestWithNoAcceptHeader() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class).run((context) -> MockMvcBuilders + .webAppContextSetup(context).build().perform(get("/")).andExpect(status().isNotAcceptable())); + } + + @Test + void handlesRequestWithEmptyAcceptHeader() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").header(HttpHeaders.ACCEPT, "")).andExpect(status().isNotAcceptable())); + } + + @Test + void rootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .withPropertyValues("static-path-pattern=/foo/**").run((context) -> assertThat( + context.getBean(WelcomePageNotAcceptableHandlerMapping.class).getRootHandler()).isNull()); + } + + @Test + void producesNotFoundResponseWhenThereIsNoWelcomePage() { + this.contextRunner.run((context) -> MockMvcBuilders.webAppContextSetup(context).build() + .perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isNotFound())); + } + + @Configuration(proxyBeanMethods = false) + static class HandlerMappingConfiguration { + + @Bean + WelcomePageNotAcceptableHandlerMapping handlerMapping(ApplicationContext applicationContext, + ObjectProvider templateAvailabilityProviders, + ObjectProvider staticIndexPage, + @Value("${static-path-pattern:/**}") String staticPathPattern) { + return new WelcomePageNotAcceptableHandlerMapping( + templateAvailabilityProviders + .getIfAvailable(() -> new TemplateAvailabilityProviders(applicationContext)), + applicationContext, staticIndexPage.getIfAvailable(), staticPathPattern); + } + + } + + @Configuration(proxyBeanMethods = false) + static class StaticResourceConfiguration { + + @Bean + Resource staticIndexPage() { + return new FileSystemResource("src/test/resources/welcome-page/index.html"); + } + + } + +}