Skip to content

Commit f9e3e0d

Browse files
committed
Register default resource path using a Resource
Update `WebMvcAutoConfiguration` so that the default "/" resource path is registered directly as a `ServletContextResource`. Closes gh-24745
1 parent 46629ef commit f9e3e0d

File tree

4 files changed

+118
-75
lines changed

4 files changed

+118
-75
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java

Lines changed: 110 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,13 +17,14 @@
1717
package org.springframework.boot.autoconfigure.web.servlet;
1818

1919
import java.time.Duration;
20-
import java.util.Arrays;
20+
import java.util.HashSet;
2121
import java.util.List;
2222
import java.util.ListIterator;
2323
import java.util.Map;
24-
import java.util.Optional;
24+
import java.util.Set;
2525

2626
import javax.servlet.Servlet;
27+
import javax.servlet.ServletContext;
2728

2829
import org.apache.commons.logging.Log;
2930
import org.apache.commons.logging.LogFactory;
@@ -70,10 +71,10 @@
7071
import org.springframework.core.task.AsyncTaskExecutor;
7172
import org.springframework.format.FormatterRegistry;
7273
import org.springframework.format.support.FormattingConversionService;
73-
import org.springframework.http.CacheControl;
7474
import org.springframework.http.MediaType;
7575
import org.springframework.http.converter.HttpMessageConverter;
7676
import org.springframework.util.ClassUtils;
77+
import org.springframework.util.PathMatcher;
7778
import org.springframework.validation.DefaultMessageCodesResolver;
7879
import org.springframework.validation.MessageCodesResolver;
7980
import org.springframework.validation.Validator;
@@ -85,11 +86,13 @@
8586
import org.springframework.web.context.request.NativeWebRequest;
8687
import org.springframework.web.context.request.RequestAttributes;
8788
import org.springframework.web.context.request.RequestContextListener;
89+
import org.springframework.web.context.support.ServletContextResource;
8890
import org.springframework.web.filter.FormContentFilter;
8991
import org.springframework.web.filter.HiddenHttpMethodFilter;
9092
import org.springframework.web.filter.RequestContextFilter;
9193
import org.springframework.web.servlet.DispatcherServlet;
9294
import org.springframework.web.servlet.HandlerExceptionResolver;
95+
import org.springframework.web.servlet.HandlerMapping;
9396
import org.springframework.web.servlet.LocaleResolver;
9497
import org.springframework.web.servlet.View;
9598
import org.springframework.web.servlet.ViewResolver;
@@ -104,19 +107,22 @@
104107
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
105108
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
106109
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
110+
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
107111
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
108112
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
109113
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
110114
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
111115
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
112116
import org.springframework.web.servlet.resource.AppCacheManifestTransformer;
113117
import org.springframework.web.servlet.resource.EncodedResourceResolver;
118+
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
114119
import org.springframework.web.servlet.resource.ResourceResolver;
115120
import org.springframework.web.servlet.resource.ResourceUrlProvider;
116121
import org.springframework.web.servlet.resource.VersionResourceResolver;
117122
import org.springframework.web.servlet.view.BeanNameViewResolver;
118123
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
119124
import org.springframework.web.servlet.view.InternalResourceViewResolver;
125+
import org.springframework.web.util.UrlPathHelper;
120126

121127
/**
122128
* {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebMvc Web MVC}.
@@ -151,7 +157,7 @@ public class WebMvcAutoConfiguration {
151157
*/
152158
public static final String DEFAULT_SUFFIX = "";
153159

154-
private static final String[] SERVLET_LOCATIONS = { "/" };
160+
private static final String SERVLET_LOCATION = "/";
155161

156162
@Bean
157163
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@@ -167,13 +173,6 @@ public OrderedFormContentFilter formContentFilter() {
167173
return new OrderedFormContentFilter();
168174
}
169175

170-
static String[] getResourceLocations(String[] staticLocations) {
171-
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
172-
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
173-
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
174-
return locations;
175-
}
176-
177176
// Defined as a nested config to ensure WebMvcConfigurer is not read when not
178177
// on the classpath
179178
@Configuration(proxyBeanMethods = false)
@@ -182,26 +181,17 @@ static String[] getResourceLocations(String[] staticLocations) {
182181
@Order(0)
183182
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
184183

185-
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
186-
187-
private final ResourceProperties resourceProperties;
188-
189184
private final WebMvcProperties mvcProperties;
190185

191186
private final ListableBeanFactory beanFactory;
192187

193188
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
194189

195-
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
196-
197-
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
198-
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
199-
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
200-
this.resourceProperties = resourceProperties;
190+
public WebMvcAutoConfigurationAdapter(WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
191+
ObjectProvider<HttpMessageConverters> messageConvertersProvider) {
201192
this.mvcProperties = mvcProperties;
202193
this.beanFactory = beanFactory;
203194
this.messageConvertersProvider = messageConvertersProvider;
204-
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
205195
}
206196

207197
@Override
@@ -303,37 +293,6 @@ public void addFormatters(FormatterRegistry registry) {
303293
ApplicationConversionService.addBeans(registry, this.beanFactory);
304294
}
305295

306-
@Override
307-
public void addResourceHandlers(ResourceHandlerRegistry registry) {
308-
if (!this.resourceProperties.isAddMappings()) {
309-
logger.debug("Default resource handling disabled");
310-
return;
311-
}
312-
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
313-
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
314-
if (!registry.hasMappingForPattern("/webjars/**")) {
315-
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
316-
.addResourceLocations("classpath:/META-INF/resources/webjars/")
317-
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
318-
}
319-
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
320-
if (!registry.hasMappingForPattern(staticPathPattern)) {
321-
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
322-
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
323-
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
324-
}
325-
}
326-
327-
private Integer getSeconds(Duration cachePeriod) {
328-
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
329-
}
330-
331-
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
332-
if (this.resourceHandlerRegistrationCustomizer != null) {
333-
this.resourceHandlerRegistrationCustomizer.customize(registration);
334-
}
335-
}
336-
337296
@Bean
338297
@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
339298
@ConditionalOnMissingFilterBean(RequestContextFilter.class)
@@ -349,22 +308,31 @@ public static RequestContextFilter requestContextFilter() {
349308
@Configuration(proxyBeanMethods = false)
350309
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
351310

311+
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
312+
352313
private final ResourceProperties resourceProperties;
353314

354315
private final WebMvcProperties mvcProperties;
355316

356-
private final ListableBeanFactory beanFactory;
357-
358317
private final WebMvcRegistrations mvcRegistrations;
359318

319+
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
320+
360321
private ResourceLoader resourceLoader;
361322

323+
private final ListableBeanFactory beanFactory;
324+
325+
private final Set<String> autoConfiguredResourceHandlers = new HashSet<>();
326+
362327
public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
363328
ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
364-
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
329+
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
330+
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
331+
ListableBeanFactory beanFactory) {
365332
this.resourceProperties = resourceProperties;
366333
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
367334
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
335+
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
368336
this.beanFactory = beanFactory;
369337
}
370338

@@ -401,6 +369,72 @@ public RequestMappingHandlerMapping requestMappingHandlerMapping(
401369
resourceUrlProvider);
402370
}
403371

372+
@Bean
373+
@Override
374+
public HandlerMapping resourceHandlerMapping(UrlPathHelper urlPathHelper, PathMatcher pathMatcher,
375+
ContentNegotiationManager contentNegotiationManager, FormattingConversionService conversionService,
376+
ResourceUrlProvider resourceUrlProvider) {
377+
HandlerMapping mapping = super.resourceHandlerMapping(urlPathHelper, pathMatcher, contentNegotiationManager,
378+
conversionService, resourceUrlProvider);
379+
if (mapping instanceof SimpleUrlHandlerMapping) {
380+
addServletContextResourceHandlerMapping((SimpleUrlHandlerMapping) mapping);
381+
}
382+
return mapping;
383+
}
384+
385+
private void addServletContextResourceHandlerMapping(SimpleUrlHandlerMapping mapping) {
386+
Map<String, ?> urlMap = mapping.getUrlMap();
387+
String pattern = this.mvcProperties.getStaticPathPattern();
388+
Object handler = urlMap.get(pattern);
389+
if (handler instanceof ResourceHttpRequestHandler
390+
&& this.autoConfiguredResourceHandlers.contains(pattern)) {
391+
addServletContextResourceHandlerMapping((ResourceHttpRequestHandler) handler);
392+
}
393+
}
394+
395+
private void addServletContextResourceHandlerMapping(ResourceHttpRequestHandler handler) {
396+
ServletContext servletContext = getServletContext();
397+
if (servletContext != null) {
398+
List<Resource> locations = handler.getLocations();
399+
locations.add(new ServletContextResource(servletContext, SERVLET_LOCATION));
400+
}
401+
}
402+
403+
@Override
404+
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
405+
super.addResourceHandlers(registry);
406+
if (!this.resourceProperties.isAddMappings()) {
407+
logger.debug("Default resource handling disabled");
408+
return;
409+
}
410+
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
411+
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(),
412+
this.resourceProperties.getStaticLocations());
413+
414+
}
415+
416+
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
417+
if (registry.hasMappingForPattern(pattern)) {
418+
return;
419+
}
420+
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
421+
registration.addResourceLocations(locations);
422+
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
423+
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
424+
customizeResourceHandlerRegistration(registration);
425+
this.autoConfiguredResourceHandlers.add(pattern);
426+
}
427+
428+
private Integer getSeconds(Duration cachePeriod) {
429+
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
430+
}
431+
432+
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
433+
if (this.resourceHandlerRegistrationCustomizer != null) {
434+
this.resourceHandlerRegistrationCustomizer.customize(registration);
435+
}
436+
}
437+
404438
@Bean
405439
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
406440
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
@@ -412,22 +446,34 @@ public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext ap
412446
return welcomePageHandlerMapping;
413447
}
414448

415-
private Optional<Resource> getWelcomePage() {
416-
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
417-
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
449+
private Resource getWelcomePage() {
450+
for (String location : this.resourceProperties.getStaticLocations()) {
451+
Resource indexHtml = getIndexHtml(location);
452+
if (indexHtml != null) {
453+
return indexHtml;
454+
}
455+
}
456+
ServletContext servletContext = getServletContext();
457+
if (servletContext != null) {
458+
return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
459+
}
460+
return null;
418461
}
419462

420463
private Resource getIndexHtml(String location) {
421-
return this.resourceLoader.getResource(location + "index.html");
464+
return getIndexHtml(this.resourceLoader.getResource(location));
422465
}
423466

424-
private boolean isReadable(Resource resource) {
467+
private Resource getIndexHtml(Resource location) {
425468
try {
426-
return resource.exists() && (resource.getURL() != null);
469+
Resource resource = location.createRelative("index.html");
470+
if (resource.exists() && (resource.getURL() != null)) {
471+
return resource;
472+
}
427473
}
428474
catch (Exception ex) {
429-
return false;
430475
}
476+
return null;
431477
}
432478

433479
@Bean

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
1818

1919
import java.util.Collections;
2020
import java.util.List;
21-
import java.util.Optional;
2221

2322
import javax.servlet.http.HttpServletRequest;
2423

@@ -49,9 +48,9 @@ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
4948
private static final List<MediaType> MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL);
5049

5150
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
52-
ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
53-
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
54-
logger.info("Adding welcome page: " + welcomePage.get());
51+
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
52+
if (welcomePage != null && "/**".equals(staticPathPattern)) {
53+
logger.info("Adding welcome page: " + welcomePage);
5554
setRootViewName("forward:index.html");
5655
}
5756
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -814,7 +814,7 @@ private void assertCacheControl(AssertableWebApplicationContext context) {
814814
protected Map<String, List<Resource>> getResourceMappingLocations(ApplicationContext context) {
815815
Object bean = context.getBean("resourceHandlerMapping");
816816
if (bean instanceof HandlerMapping) {
817-
return getMappingLocations(context.getBean("resourceHandlerMapping", HandlerMapping.class));
817+
return getMappingLocations((HandlerMapping) bean);
818818
}
819819
assertThat(bean.toString()).isEqualTo("null");
820820
return Collections.emptyMap();

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
1818

1919
import java.util.Collections;
2020
import java.util.Map;
21-
import java.util.Optional;
2221

2322
import javax.servlet.http.HttpServletRequest;
2423
import javax.servlet.http.HttpServletResponse;
@@ -162,8 +161,7 @@ WelcomePageHandlerMapping handlerMapping(ApplicationContext applicationContext,
162161
return new WelcomePageHandlerMapping(
163162
templateAvailabilityProviders
164163
.getIfAvailable(() -> new TemplateAvailabilityProviders(applicationContext)),
165-
applicationContext, Optional.ofNullable(staticIndexPage.getIfAvailable()), staticPathPattern);
166-
164+
applicationContext, staticIndexPage.getIfAvailable(), staticPathPattern);
167165
}
168166

169167
}

0 commit comments

Comments
 (0)