From 1a76f7e9c20a0fb87c50a71a2444f9c61619c3bb Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 4 Mar 2016 11:51:30 -0500 Subject: [PATCH] Polish static resource handling --- .../resource/ResourceHttpRequestHandler.java | 61 +++++++++-------- .../resource/ResourceUrlProviderTests.java | 65 ++++++++----------- 2 files changed, 57 insertions(+), 69 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 7b56747158f..1f58af40f9a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -57,40 +57,35 @@ import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.support.WebContentGenerator; /** - * {@link HttpRequestHandler} that serves static resources optimized for superior browser performance - * (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings - * ({@linkplain #setCacheSeconds "cacheSeconds" property}, last-modified support). + * {@code HttpRequestHandler} that serves static resources in an optimized way + * according to the guidelines of Page Speed, YSlow, etc. * - *

The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource} - * locations from which static resources are allowed to be served by this handler. For a given request, - * the list of locations will be consulted in order for the presence of the requested resource, and the - * first found match will be written to the response, with a HTTP Caching headers - * set as configured. The handler also properly evaluates the {@code Last-Modified} header - * (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary - * overhead for resources that are already cached by the client. The use of {@code Resource} locations - * allows resource requests to easily be mapped to locations other than the web application root. - * For example, resources could be served from a classpath location such as - * "classpath:/META-INF/public-web-resources/", allowing convenient packaging and serving of resources - * such as a JavaScript library from within jar files. + *

The {@linkplain #setLocations "locations"} property takes a list of Spring + * {@link Resource} locations from which static resources are allowed to + * be served by this handler. Resources could be served from a classpath location, + * e.g. "classpath:/META-INF/public-web-resources/", allowing convenient packaging + * and serving of resources such as .js, .css, and others in jar files. * - *

To ensure that users with a primed browser cache get the latest changes to application-specific - * resources upon deployment of new versions of the application, it is recommended that a version - * string is used in the URL mapping pattern that selects this handler. Such patterns can be easily - * parameterized using Spring EL. See the reference manual for further examples of this approach. + *

This request handler may also be configured with a + * {@link #setResourceResolvers(List) resourcesResolver} and + * {@link #setResourceTransformers(List) resourceTransformer} chains to support + * arbitrary resolution and transformation of resources being served. By default a + * {@link PathResourceResolver} simply finds resources based on the configured + * "locations". An application can configure additional resolvers and + * transformers such as the {@link VersionResourceResolver} which can resolve + * and prepare URLs for resources with a version in the URL. * - *

For various front-end needs — such as ensuring that users with a primed browser cache - * get the latest changes, or serving variations of resources (e.g., minified versions) — - * {@link org.springframework.web.servlet.resource.ResourceResolver}s can be configured. - * - *

This handler can be configured through use of a - * {@link org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry} - * or the {@code } XML configuration element. + *

This handler also properly evaluates the {@code Last-Modified} header (if + * present) so that a {@code 304} status code will be returned as appropriate, + * avoiding unnecessary overhead for resources that are already cached by the + * client. * * @author Keith Donald * @author Jeremy Grelle * @author Juergen Hoeller * @author Arjen Poutsma * @author Brian Clozel + * @author Rossen Stoyanchev * @since 3.0.4 */ public class ResourceHttpRequestHandler extends WebContentGenerator @@ -113,7 +108,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator public ResourceHttpRequestHandler() { super(HttpMethod.GET.name(), HttpMethod.HEAD.name()); - this.resourceResolvers.add(new PathResourceResolver()); } @@ -168,6 +162,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator return this.resourceTransformers; } + /** + * Specify the CORS configuration for resources served by this handler. + *

By default this is not set in which allows cross-origin requests. + */ public void setCorsConfiguration(CorsConfiguration corsConfiguration) { this.corsConfiguration = corsConfiguration; } @@ -184,15 +182,16 @@ public class ResourceHttpRequestHandler extends WebContentGenerator logger.warn("Locations list is empty. No resources will be served unless a " + "custom ResourceResolver is configured as an alternative to PathResourceResolver."); } + if (this.resourceResolvers.isEmpty()) { + this.resourceResolvers.add(new PathResourceResolver()); + } initAllowedLocations(); } /** - * Look for a {@link org.springframework.web.servlet.resource.PathResourceResolver} - * among the {@link #getResourceResolvers() resource resolvers} and configure - * its {@code "allowedLocations"} to match the value of the - * {@link #setLocations(java.util.List) locations} property unless the "allowed - * locations" of the {@code PathResourceResolver} is non-empty. + * Look for a {@code PathResourceResolver} among the configured resource + * resolvers and set its {@code allowedLocations} property (if empty) to + * match the {@link #setLocations locations} configured on this class. */ protected void initAllowedLocations() { if (CollectionUtils.isEmpty(this.locations)) { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java index bd0a6ac911f..fbb222c01b7 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-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. @@ -45,45 +45,39 @@ import static org.junit.Assert.*; */ public class ResourceUrlProviderTests { - private List locations; + private final List locations = new ArrayList<>(); - private ResourceUrlProvider translator; + private final ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); - private ResourceHttpRequestHandler handler; + private final Map handlerMap = new HashMap<>(); - private Map handlerMap; + private final ResourceUrlProvider urlProvider = new ResourceUrlProvider(); @Before - public void setUp() { - this.locations = new ArrayList(); + public void setUp() throws Exception { this.locations.add(new ClassPathResource("test/", getClass())); this.locations.add(new ClassPathResource("testalternatepath/", getClass())); - - this.handler = new ResourceHttpRequestHandler(); this.handler.setLocations(locations); - - this.handlerMap = new HashMap(); + this.handler.afterPropertiesSet(); this.handlerMap.put("/resources/**", this.handler); + this.urlProvider.setHandlerMap(this.handlerMap); } + @Test public void getStaticResourceUrl() { - initTranslator(); - - String url = this.translator.getForLookupPath("/resources/foo.css"); + String url = this.urlProvider.getForLookupPath("/resources/foo.css"); assertEquals("/resources/foo.css", url); } - // SPR-13374 - @Test + @Test // SPR-13374 public void getStaticResourceUrlRequestWithRequestParams() { - initTranslator(); MockHttpServletRequest request = new MockHttpServletRequest(); request.setContextPath("/"); request.setRequestURI("/"); - String url = this.translator.getForRequestUrl(request, "/resources/foo.css?foo=bar&url=http://example.org"); + String url = this.urlProvider.getForRequestUrl(request, "/resources/foo.css?foo=bar&url=http://example.org"); assertEquals("/resources/foo.css?foo=bar&url=http://example.org", url); } @@ -94,23 +88,16 @@ public class ResourceUrlProviderTests { VersionResourceResolver versionResolver = new VersionResourceResolver(); versionResolver.setStrategyMap(versionStrategyMap); - List resolvers = new ArrayList(); + List resolvers = new ArrayList<>(); resolvers.add(versionResolver); resolvers.add(new PathResourceResolver()); this.handler.setResourceResolvers(resolvers); - initTranslator(); - String url = this.translator.getForLookupPath("/resources/foo.css"); + String url = this.urlProvider.getForLookupPath("/resources/foo.css"); assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url); } - private void initTranslator() { - this.translator = new ResourceUrlProvider(); - this.translator.setHandlerMap(this.handlerMap); - } - - // SPR-12647 - @Test + @Test // SPR-12647 public void bestPatternMatch() throws Exception { ResourceHttpRequestHandler otherHandler = new ResourceHttpRequestHandler(); otherHandler.setLocations(this.locations); @@ -119,36 +106,38 @@ public class ResourceUrlProviderTests { VersionResourceResolver versionResolver = new VersionResourceResolver(); versionResolver.setStrategyMap(versionStrategyMap); - List resolvers = new ArrayList(); + List resolvers = new ArrayList<>(); resolvers.add(versionResolver); resolvers.add(new PathResourceResolver()); otherHandler.setResourceResolvers(resolvers); this.handlerMap.put("/resources/*.css", otherHandler); - initTranslator(); + this.urlProvider.setHandlerMap(this.handlerMap); - String url = this.translator.getForLookupPath("/resources/foo.css"); + String url = this.urlProvider.getForLookupPath("/resources/foo.css"); assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url); } - // SPR-12592 - @Test + @Test // SPR-12592 public void initializeOnce() throws Exception { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setServletContext(new MockServletContext()); context.register(HandlerMappingConfiguration.class); context.refresh(); - ResourceUrlProvider translator = context.getBean(ResourceUrlProvider.class); - assertThat(translator.getHandlerMap(), Matchers.hasKey("/resources/**")); - assertFalse(translator.isAutodetect()); + + ResourceUrlProvider urlProviderBean = context.getBean(ResourceUrlProvider.class); + assertThat(urlProviderBean.getHandlerMap(), Matchers.hasKey("/resources/**")); + assertFalse(urlProviderBean.isAutodetect()); } - @Configuration + + @Configuration @SuppressWarnings("unused") public static class HandlerMappingConfiguration { + @Bean public SimpleUrlHandlerMapping simpleUrlHandlerMapping() { ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); - HashMap handlerMap = new HashMap(); + HashMap handlerMap = new HashMap<>(); handlerMap.put("/resources/**", handler); SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping(); hm.setUrlMap(handlerMap);