1
1
/*
2
- * Copyright 2012-2020 the original author or authors.
2
+ * Copyright 2012-2021 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .boot .autoconfigure .web .servlet ;
18
18
19
19
import java .time .Duration ;
20
- import java .util .Arrays ;
20
+ import java .util .HashSet ;
21
21
import java .util .List ;
22
22
import java .util .ListIterator ;
23
23
import java .util .Map ;
24
- import java .util .Optional ;
24
+ import java .util .Set ;
25
25
26
26
import javax .servlet .Servlet ;
27
+ import javax .servlet .ServletContext ;
27
28
28
29
import org .apache .commons .logging .Log ;
29
30
import org .apache .commons .logging .LogFactory ;
73
74
import org .springframework .core .task .AsyncTaskExecutor ;
74
75
import org .springframework .format .FormatterRegistry ;
75
76
import org .springframework .format .support .FormattingConversionService ;
76
- import org .springframework .http .CacheControl ;
77
77
import org .springframework .http .MediaType ;
78
78
import org .springframework .http .converter .HttpMessageConverter ;
79
79
import org .springframework .util .ClassUtils ;
80
+ import org .springframework .util .PathMatcher ;
80
81
import org .springframework .validation .DefaultMessageCodesResolver ;
81
82
import org .springframework .validation .MessageCodesResolver ;
82
83
import org .springframework .validation .Validator ;
88
89
import org .springframework .web .context .request .NativeWebRequest ;
89
90
import org .springframework .web .context .request .RequestAttributes ;
90
91
import org .springframework .web .context .request .RequestContextListener ;
92
+ import org .springframework .web .context .support .ServletContextResource ;
91
93
import org .springframework .web .filter .FormContentFilter ;
92
94
import org .springframework .web .filter .HiddenHttpMethodFilter ;
93
95
import org .springframework .web .filter .RequestContextFilter ;
94
96
import org .springframework .web .servlet .DispatcherServlet ;
95
97
import org .springframework .web .servlet .HandlerExceptionResolver ;
98
+ import org .springframework .web .servlet .HandlerMapping ;
96
99
import org .springframework .web .servlet .LocaleResolver ;
97
100
import org .springframework .web .servlet .View ;
98
101
import org .springframework .web .servlet .ViewResolver ;
107
110
import org .springframework .web .servlet .config .annotation .WebMvcConfigurationSupport ;
108
111
import org .springframework .web .servlet .config .annotation .WebMvcConfigurer ;
109
112
import org .springframework .web .servlet .handler .AbstractHandlerExceptionResolver ;
113
+ import org .springframework .web .servlet .handler .SimpleUrlHandlerMapping ;
110
114
import org .springframework .web .servlet .i18n .AcceptHeaderLocaleResolver ;
111
115
import org .springframework .web .servlet .i18n .FixedLocaleResolver ;
112
116
import org .springframework .web .servlet .mvc .method .annotation .ExceptionHandlerExceptionResolver ;
113
117
import org .springframework .web .servlet .mvc .method .annotation .RequestMappingHandlerAdapter ;
114
118
import org .springframework .web .servlet .mvc .method .annotation .RequestMappingHandlerMapping ;
115
119
import org .springframework .web .servlet .resource .AppCacheManifestTransformer ;
116
120
import org .springframework .web .servlet .resource .EncodedResourceResolver ;
121
+ import org .springframework .web .servlet .resource .ResourceHttpRequestHandler ;
117
122
import org .springframework .web .servlet .resource .ResourceResolver ;
118
123
import org .springframework .web .servlet .resource .ResourceUrlProvider ;
119
124
import org .springframework .web .servlet .resource .VersionResourceResolver ;
@@ -155,7 +160,7 @@ public class WebMvcAutoConfiguration {
155
160
*/
156
161
public static final String DEFAULT_SUFFIX = "" ;
157
162
158
- private static final String [] SERVLET_LOCATIONS = { "/" } ;
163
+ private static final String SERVLET_LOCATION = "/" ;
159
164
160
165
@ Bean
161
166
@ ConditionalOnMissingBean (HiddenHttpMethodFilter .class )
@@ -171,13 +176,6 @@ public OrderedFormContentFilter formContentFilter() {
171
176
return new OrderedFormContentFilter ();
172
177
}
173
178
174
- static String [] getResourceLocations (String [] staticLocations ) {
175
- String [] locations = new String [staticLocations .length + SERVLET_LOCATIONS .length ];
176
- System .arraycopy (staticLocations , 0 , locations , 0 , staticLocations .length );
177
- System .arraycopy (SERVLET_LOCATIONS , 0 , locations , staticLocations .length , SERVLET_LOCATIONS .length );
178
- return locations ;
179
- }
180
-
181
179
// Defined as a nested config to ensure WebMvcConfigurer is not read when not
182
180
// on the classpath
183
181
@ Configuration (proxyBeanMethods = false )
@@ -186,10 +184,6 @@ static String[] getResourceLocations(String[] staticLocations) {
186
184
@ Order (0 )
187
185
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
188
186
189
- private static final Log logger = LogFactory .getLog (WebMvcConfigurer .class );
190
-
191
- private final ResourceProperties resourceProperties ;
192
-
193
187
private final WebMvcProperties mvcProperties ;
194
188
195
189
private final ListableBeanFactory beanFactory ;
@@ -202,12 +196,11 @@ public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
202
196
203
197
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer ;
204
198
205
- public WebMvcAutoConfigurationAdapter (ResourceProperties resourceProperties , WebMvcProperties mvcProperties ,
206
- ListableBeanFactory beanFactory , ObjectProvider <HttpMessageConverters > messageConvertersProvider ,
199
+ public WebMvcAutoConfigurationAdapter (WebMvcProperties mvcProperties , ListableBeanFactory beanFactory ,
200
+ ObjectProvider <HttpMessageConverters > messageConvertersProvider ,
207
201
ObjectProvider <ResourceHandlerRegistrationCustomizer > resourceHandlerRegistrationCustomizerProvider ,
208
202
ObjectProvider <DispatcherServletPath > dispatcherServletPath ,
209
203
ObjectProvider <ServletRegistrationBean <?>> servletRegistrations ) {
210
- this .resourceProperties = resourceProperties ;
211
204
this .mvcProperties = mvcProperties ;
212
205
this .beanFactory = beanFactory ;
213
206
this .messageConvertersProvider = messageConvertersProvider ;
@@ -328,37 +321,6 @@ public void addFormatters(FormatterRegistry registry) {
328
321
ApplicationConversionService .addBeans (registry , this .beanFactory );
329
322
}
330
323
331
- @ Override
332
- public void addResourceHandlers (ResourceHandlerRegistry registry ) {
333
- if (!this .resourceProperties .isAddMappings ()) {
334
- logger .debug ("Default resource handling disabled" );
335
- return ;
336
- }
337
- Duration cachePeriod = this .resourceProperties .getCache ().getPeriod ();
338
- CacheControl cacheControl = this .resourceProperties .getCache ().getCachecontrol ().toHttpCacheControl ();
339
- if (!registry .hasMappingForPattern ("/webjars/**" )) {
340
- customizeResourceHandlerRegistration (registry .addResourceHandler ("/webjars/**" )
341
- .addResourceLocations ("classpath:/META-INF/resources/webjars/" )
342
- .setCachePeriod (getSeconds (cachePeriod )).setCacheControl (cacheControl ));
343
- }
344
- String staticPathPattern = this .mvcProperties .getStaticPathPattern ();
345
- if (!registry .hasMappingForPattern (staticPathPattern )) {
346
- customizeResourceHandlerRegistration (registry .addResourceHandler (staticPathPattern )
347
- .addResourceLocations (getResourceLocations (this .resourceProperties .getStaticLocations ()))
348
- .setCachePeriod (getSeconds (cachePeriod )).setCacheControl (cacheControl ));
349
- }
350
- }
351
-
352
- private Integer getSeconds (Duration cachePeriod ) {
353
- return (cachePeriod != null ) ? (int ) cachePeriod .getSeconds () : null ;
354
- }
355
-
356
- private void customizeResourceHandlerRegistration (ResourceHandlerRegistration registration ) {
357
- if (this .resourceHandlerRegistrationCustomizer != null ) {
358
- this .resourceHandlerRegistrationCustomizer .customize (registration );
359
- }
360
- }
361
-
362
324
@ Bean
363
325
@ ConditionalOnMissingBean ({ RequestContextListener .class , RequestContextFilter .class })
364
326
@ ConditionalOnMissingFilterBean (RequestContextFilter .class )
@@ -374,22 +336,31 @@ public static RequestContextFilter requestContextFilter() {
374
336
@ Configuration (proxyBeanMethods = false )
375
337
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
376
338
339
+ private static final Log logger = LogFactory .getLog (WebMvcConfigurer .class );
340
+
377
341
private final ResourceProperties resourceProperties ;
378
342
379
343
private final WebMvcProperties mvcProperties ;
380
344
381
- private final ListableBeanFactory beanFactory ;
382
-
383
345
private final WebMvcRegistrations mvcRegistrations ;
384
346
347
+ private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer ;
348
+
385
349
private ResourceLoader resourceLoader ;
386
350
351
+ private final ListableBeanFactory beanFactory ;
352
+
353
+ private final Set <String > autoConfiguredResourceHandlers = new HashSet <>();
354
+
387
355
public EnableWebMvcConfiguration (ResourceProperties resourceProperties ,
388
356
ObjectProvider <WebMvcProperties > mvcPropertiesProvider ,
389
- ObjectProvider <WebMvcRegistrations > mvcRegistrationsProvider , ListableBeanFactory beanFactory ) {
357
+ ObjectProvider <WebMvcRegistrations > mvcRegistrationsProvider ,
358
+ ObjectProvider <ResourceHandlerRegistrationCustomizer > resourceHandlerRegistrationCustomizerProvider ,
359
+ ListableBeanFactory beanFactory ) {
390
360
this .resourceProperties = resourceProperties ;
391
361
this .mvcProperties = mvcPropertiesProvider .getIfAvailable ();
392
362
this .mvcRegistrations = mvcRegistrationsProvider .getIfUnique ();
363
+ this .resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider .getIfAvailable ();
393
364
this .beanFactory = beanFactory ;
394
365
}
395
366
@@ -429,6 +400,72 @@ public RequestMappingHandlerMapping requestMappingHandlerMapping(
429
400
resourceUrlProvider );
430
401
}
431
402
403
+ @ Bean
404
+ @ Override
405
+ public HandlerMapping resourceHandlerMapping (UrlPathHelper urlPathHelper , PathMatcher pathMatcher ,
406
+ ContentNegotiationManager contentNegotiationManager , FormattingConversionService conversionService ,
407
+ ResourceUrlProvider resourceUrlProvider ) {
408
+ HandlerMapping mapping = super .resourceHandlerMapping (urlPathHelper , pathMatcher , contentNegotiationManager ,
409
+ conversionService , resourceUrlProvider );
410
+ if (mapping instanceof SimpleUrlHandlerMapping ) {
411
+ addServletContextResourceHandlerMapping ((SimpleUrlHandlerMapping ) mapping );
412
+ }
413
+ return mapping ;
414
+ }
415
+
416
+ private void addServletContextResourceHandlerMapping (SimpleUrlHandlerMapping mapping ) {
417
+ Map <String , ?> urlMap = mapping .getUrlMap ();
418
+ String pattern = this .mvcProperties .getStaticPathPattern ();
419
+ Object handler = urlMap .get (pattern );
420
+ if (handler instanceof ResourceHttpRequestHandler
421
+ && this .autoConfiguredResourceHandlers .contains (pattern )) {
422
+ addServletContextResourceHandlerMapping ((ResourceHttpRequestHandler ) handler );
423
+ }
424
+ }
425
+
426
+ private void addServletContextResourceHandlerMapping (ResourceHttpRequestHandler handler ) {
427
+ ServletContext servletContext = getServletContext ();
428
+ if (servletContext != null ) {
429
+ List <Resource > locations = handler .getLocations ();
430
+ locations .add (new ServletContextResource (servletContext , SERVLET_LOCATION ));
431
+ }
432
+ }
433
+
434
+ @ Override
435
+ protected void addResourceHandlers (ResourceHandlerRegistry registry ) {
436
+ super .addResourceHandlers (registry );
437
+ if (!this .resourceProperties .isAddMappings ()) {
438
+ logger .debug ("Default resource handling disabled" );
439
+ return ;
440
+ }
441
+ addResourceHandler (registry , "/webjars/**" , "classpath:/META-INF/resources/webjars/" );
442
+ addResourceHandler (registry , this .mvcProperties .getStaticPathPattern (),
443
+ this .resourceProperties .getStaticLocations ());
444
+
445
+ }
446
+
447
+ private void addResourceHandler (ResourceHandlerRegistry registry , String pattern , String ... locations ) {
448
+ if (registry .hasMappingForPattern (pattern )) {
449
+ return ;
450
+ }
451
+ ResourceHandlerRegistration registration = registry .addResourceHandler (pattern );
452
+ registration .addResourceLocations (locations );
453
+ registration .setCachePeriod (getSeconds (this .resourceProperties .getCache ().getPeriod ()));
454
+ registration .setCacheControl (this .resourceProperties .getCache ().getCachecontrol ().toHttpCacheControl ());
455
+ customizeResourceHandlerRegistration (registration );
456
+ this .autoConfiguredResourceHandlers .add (pattern );
457
+ }
458
+
459
+ private Integer getSeconds (Duration cachePeriod ) {
460
+ return (cachePeriod != null ) ? (int ) cachePeriod .getSeconds () : null ;
461
+ }
462
+
463
+ private void customizeResourceHandlerRegistration (ResourceHandlerRegistration registration ) {
464
+ if (this .resourceHandlerRegistrationCustomizer != null ) {
465
+ this .resourceHandlerRegistrationCustomizer .customize (registration );
466
+ }
467
+ }
468
+
432
469
@ Bean
433
470
public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext ,
434
471
FormattingConversionService mvcConversionService , ResourceUrlProvider mvcResourceUrlProvider ) {
@@ -440,22 +477,34 @@ public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext ap
440
477
return welcomePageHandlerMapping ;
441
478
}
442
479
443
- private Optional <Resource > getWelcomePage () {
444
- String [] locations = getResourceLocations (this .resourceProperties .getStaticLocations ());
445
- return Arrays .stream (locations ).map (this ::getIndexHtml ).filter (this ::isReadable ).findFirst ();
480
+ private Resource getWelcomePage () {
481
+ for (String location : this .resourceProperties .getStaticLocations ()) {
482
+ Resource indexHtml = getIndexHtml (location );
483
+ if (indexHtml != null ) {
484
+ return indexHtml ;
485
+ }
486
+ }
487
+ ServletContext servletContext = getServletContext ();
488
+ if (servletContext != null ) {
489
+ return getIndexHtml (new ServletContextResource (servletContext , SERVLET_LOCATION ));
490
+ }
491
+ return null ;
446
492
}
447
493
448
494
private Resource getIndexHtml (String location ) {
449
- return this .resourceLoader .getResource (location + "index.html" );
495
+ return getIndexHtml ( this .resourceLoader .getResource (location ) );
450
496
}
451
497
452
- private boolean isReadable (Resource resource ) {
498
+ private Resource getIndexHtml (Resource location ) {
453
499
try {
454
- return resource .exists () && (resource .getURL () != null );
500
+ Resource resource = location .createRelative ("index.html" );
501
+ if (resource .exists () && (resource .getURL () != null )) {
502
+ return resource ;
503
+ }
455
504
}
456
505
catch (Exception ex ) {
457
- return false ;
458
506
}
507
+ return null ;
459
508
}
460
509
461
510
@ Bean
0 commit comments