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 5dd26b78ec8..b822a13ca41 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 @@ -17,14 +17,15 @@ package org.springframework.boot.autoconfigure.web.servlet; import java.time.Duration; -import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; -import java.util.Optional; +import java.util.Set; import javax.servlet.Servlet; +import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -74,7 +75,6 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.FormattingConversionService; -import org.springframework.http.CacheControl; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.ClassUtils; @@ -88,12 +88,14 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextListener; +import org.springframework.web.context.support.ServletContextResource; import org.springframework.web.filter.FormContentFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.filter.RequestContextFilter; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.ThemeResolver; import org.springframework.web.servlet.View; @@ -109,12 +111,14 @@ 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.SimpleUrlHandlerMapping; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver; 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; import org.springframework.web.servlet.resource.EncodedResourceResolver; +import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.servlet.resource.ResourceResolver; import org.springframework.web.servlet.resource.ResourceUrlProvider; import org.springframework.web.servlet.resource.VersionResourceResolver; @@ -158,7 +162,7 @@ public class WebMvcAutoConfiguration { */ public static final String DEFAULT_SUFFIX = ""; - private static final String[] SERVLET_LOCATIONS = { "/" }; + private static final String SERVLET_LOCATION = "/"; @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @@ -174,13 +178,6 @@ public class WebMvcAutoConfiguration { return new OrderedFormContentFilter(); } - static String[] getResourceLocations(String[] staticLocations) { - String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length]; - System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length); - System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length); - return locations; - } - // Defined as a nested config to ensure WebMvcConfigurer is not read when not // on the classpath @SuppressWarnings("deprecation") @@ -191,10 +188,6 @@ public class WebMvcAutoConfiguration { @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { - private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); - - private final Resources resourceProperties; - private final WebMvcProperties mvcProperties; private final ListableBeanFactory beanFactory; @@ -207,15 +200,11 @@ public class WebMvcAutoConfiguration { final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; - public WebMvcAutoConfigurationAdapter( - org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties, - WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, - ObjectProvider messageConvertersProvider, + public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, + ListableBeanFactory beanFactory, ObjectProvider messageConvertersProvider, ObjectProvider resourceHandlerRegistrationCustomizerProvider, ObjectProvider dispatcherServletPath, ObjectProvider> servletRegistrations) { - this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties - : webProperties.getResources(); this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; @@ -327,39 +316,6 @@ public class WebMvcAutoConfiguration { ApplicationConversionService.addBeans(registry, this.beanFactory); } - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - if (!this.resourceProperties.isAddMappings()) { - logger.debug("Default resource handling disabled"); - return; - } - Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); - CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); - if (!registry.hasMappingForPattern("/webjars/**")) { - customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") - .addResourceLocations("classpath:/META-INF/resources/webjars/") - .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl) - .setUseLastModified(this.resourceProperties.getCache().isUseLastModified())); - } - String staticPathPattern = this.mvcProperties.getStaticPathPattern(); - if (!registry.hasMappingForPattern(staticPathPattern)) { - customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) - .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) - .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl) - .setUseLastModified(this.resourceProperties.getCache().isUseLastModified())); - } - } - - private Integer getSeconds(Duration cachePeriod) { - return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null; - } - - private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) { - if (this.resourceHandlerRegistrationCustomizer != null) { - this.resourceHandlerRegistrationCustomizer.customize(registration); - } - } - @Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) @ConditionalOnMissingFilterBean(RequestContextFilter.class) @@ -376,6 +332,8 @@ public class WebMvcAutoConfiguration { @EnableConfigurationProperties(WebProperties.class) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { + private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); + private final Resources resourceProperties; private final WebMvcProperties mvcProperties; @@ -386,18 +344,25 @@ public class WebMvcAutoConfiguration { private final WebMvcRegistrations mvcRegistrations; + private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; + private ResourceLoader resourceLoader; + private final Set autoConfiguredResourceHandlers = new HashSet<>(); + @SuppressWarnings("deprecation") public EnableWebMvcConfiguration( org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties, WebMvcProperties mvcProperties, WebProperties webProperties, - ObjectProvider mvcRegistrationsProvider, ListableBeanFactory beanFactory) { + ObjectProvider mvcRegistrationsProvider, + ObjectProvider resourceHandlerRegistrationCustomizerProvider, + ListableBeanFactory beanFactory) { this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources(); this.mvcProperties = mvcProperties; this.webProperties = webProperties; this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); + this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.beanFactory = beanFactory; } @@ -437,6 +402,71 @@ public class WebMvcAutoConfiguration { resourceUrlProvider); } + @Bean + @Override + public HandlerMapping resourceHandlerMapping(ContentNegotiationManager contentNegotiationManager, + FormattingConversionService conversionService, ResourceUrlProvider resourceUrlProvider) { + HandlerMapping mapping = super.resourceHandlerMapping(contentNegotiationManager, conversionService, + resourceUrlProvider); + if (mapping instanceof SimpleUrlHandlerMapping) { + addServletContextResourceHandlerMapping((SimpleUrlHandlerMapping) mapping); + } + return mapping; + } + + private void addServletContextResourceHandlerMapping(SimpleUrlHandlerMapping mapping) { + Map urlMap = mapping.getUrlMap(); + String pattern = this.mvcProperties.getStaticPathPattern(); + Object handler = urlMap.get(pattern); + if (handler instanceof ResourceHttpRequestHandler + && this.autoConfiguredResourceHandlers.contains(pattern)) { + addServletContextResourceHandlerMapping((ResourceHttpRequestHandler) handler); + } + } + + private void addServletContextResourceHandlerMapping(ResourceHttpRequestHandler handler) { + ServletContext servletContext = getServletContext(); + if (servletContext != null) { + List locations = handler.getLocations(); + locations.add(new ServletContextResource(servletContext, SERVLET_LOCATION)); + } + } + + @Override + protected void addResourceHandlers(ResourceHandlerRegistry registry) { + super.addResourceHandlers(registry); + if (!this.resourceProperties.isAddMappings()) { + logger.debug("Default resource handling disabled"); + return; + } + addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); + addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), + this.resourceProperties.getStaticLocations()); + + } + + private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) { + if (registry.hasMappingForPattern(pattern)) { + return; + } + ResourceHandlerRegistration registration = registry.addResourceHandler(pattern); + registration.addResourceLocations(locations); + registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod())); + registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); + customizeResourceHandlerRegistration(registration); + this.autoConfiguredResourceHandlers.add(pattern); + } + + private Integer getSeconds(Duration cachePeriod) { + return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null; + } + + private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) { + if (this.resourceHandlerRegistrationCustomizer != null) { + this.resourceHandlerRegistrationCustomizer.customize(registration); + } + } + @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { @@ -480,22 +510,34 @@ public class WebMvcAutoConfiguration { return super.flashMapManager(); } - private Optional getWelcomePage() { - String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations()); - return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst(); + private Resource getWelcomePage() { + for (String location : this.resourceProperties.getStaticLocations()) { + Resource indexHtml = getIndexHtml(location); + if (indexHtml != null) { + return indexHtml; + } + } + ServletContext servletContext = getServletContext(); + if (servletContext != null) { + return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION)); + } + return null; } private Resource getIndexHtml(String location) { - return this.resourceLoader.getResource(location + "index.html"); + return getIndexHtml(this.resourceLoader.getResource(location)); } - private boolean isReadable(Resource resource) { + private Resource getIndexHtml(Resource location) { try { - return resource.exists() && (resource.getURL() != null); + Resource resource = location.createRelative("index.html"); + if (resource.exists() && (resource.getURL() != null)) { + return resource; + } } catch (Exception ex) { - return false; } + return null; } @Bean 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 7717a8a548e..2f33cbb3e9d 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-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -18,7 +18,6 @@ package org.springframework.boot.autoconfigure.web.servlet; import java.util.Collections; import java.util.List; -import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -49,9 +48,9 @@ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { private static final List MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL); WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext, Optional welcomePage, String staticPathPattern) { - if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { - logger.info("Adding welcome page: " + welcomePage.get()); + 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)) { 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 04d3bb72e0d..3e929144d28 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 @@ -1019,7 +1019,7 @@ class WebMvcAutoConfigurationTests { protected Map> getResourceMappingLocations(ApplicationContext context) { Object bean = context.getBean("resourceHandlerMapping"); if (bean instanceof HandlerMapping) { - return getMappingLocations(context.getBean("resourceHandlerMapping", HandlerMapping.class)); + return getMappingLocations((HandlerMapping) bean); } assertThat(bean.toString()).isEqualTo("null"); return Collections.emptyMap(); 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 ede657075bf..b907cdae2be 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-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -18,7 +18,6 @@ package org.springframework.boot.autoconfigure.web.servlet; import java.util.Collections; import java.util.Map; -import java.util.Optional; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -162,8 +161,7 @@ class WelcomePageHandlerMappingTests { return new WelcomePageHandlerMapping( templateAvailabilityProviders .getIfAvailable(() -> new TemplateAvailabilityProviders(applicationContext)), - applicationContext, Optional.ofNullable(staticIndexPage.getIfAvailable()), staticPathPattern); - + applicationContext, staticIndexPage.getIfAvailable(), staticPathPattern); } }