Revise charset by location support for static resources

This commit is contained in:
Rossen Stoyanchev
2017-11-17 12:33:58 -05:00
parent ce895d7a84
commit b1b5353b7f
8 changed files with 101 additions and 140 deletions

View File

@@ -16,9 +16,7 @@
package org.springframework.web.servlet.config;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -26,9 +24,6 @@ import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
@@ -65,8 +60,6 @@ public abstract class MvcNamespaceUtils {
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
private static final String URL_RESOURCE_CHARSET_PREFIX = "[charset=";
public static void registerDefaultComponents(ParserContext parserContext, @Nullable Object source) {
registerBeanNameUrlHandlerMapping(parserContext, source);
@@ -231,40 +224,4 @@ public abstract class MvcNamespaceUtils {
return null;
}
/**
* Load the {@link Resource}'s for the given locations with the given
* {@link ResourceLoader} and add them to the output list. Also for
* {@link org.springframework.core.io.UrlResource URL-based resources} (e.g. files,
* HTTP URLs, etc) this method supports a special prefix to indicate the charset
* associated with the URL so that relative paths appended to it can be encoded
* correctly, e.g. {@code [charset=Windows-31J]http://example.org/path}.
* The charsets, if any, are added to the output map.
* @since 4.3.13
*/
public static void loadResourceLocations(String[] locations, ResourceLoader resourceLoader,
List<Resource> outputLocations, Map<Resource, Charset> outputLocationCharsets) {
for (String location : locations) {
Charset charset = null;
location = location.trim();
if (location.startsWith(URL_RESOURCE_CHARSET_PREFIX)) {
int endIndex = location.indexOf("]", URL_RESOURCE_CHARSET_PREFIX.length());
if (endIndex == -1) {
throw new IllegalArgumentException("Invalid charset syntax in location: " + location);
}
String value = location.substring(URL_RESOURCE_CHARSET_PREFIX.length(), endIndex);
charset = Charset.forName(value);
location = location.substring(endIndex + 1);
}
Resource resource = resourceLoader.getResource(location);
outputLocations.add(resource);
if (charset != null) {
if (!(resource instanceof UrlResource)) {
throw new IllegalArgumentException("Unexpected charset for non-UrlResource: " + resource);
}
outputLocationCharsets.put(resource, charset);
}
}
}
}

View File

@@ -16,11 +16,6 @@
package org.springframework.web.servlet.config;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -28,7 +23,6 @@ import org.w3c.dom.Element;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
@@ -39,8 +33,6 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.CacheControl;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -172,35 +164,13 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
return null;
}
String[] locationValues = StringUtils.commaDelimitedListToStringArray(locationAttr);
if (context.getRegistry() instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory cbf = ((ConfigurableBeanFactory) context.getRegistry());
for (int i = 0; i < locationValues.length; i++) {
locationValues[i] = cbf.resolveEmbeddedValue(locationValues[i]);
}
}
ManagedList<Object> locations = new ManagedList<>();
Map<Resource, Charset> locationCharsets = new HashMap<>();
ResourceLoader resourceLoader = context.getReaderContext().getResourceLoader();
if (resourceLoader != null) {
List<Resource> resources = new ArrayList<>();
MvcNamespaceUtils.loadResourceLocations(locationValues, resourceLoader, resources, locationCharsets);
locations.addAll(resources);
}
else {
locations.addAll(Arrays.asList(locationValues));
}
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
MutablePropertyValues values = resourceHandlerDef.getPropertyValues();
values.add("urlPathHelper", pathHelperRef);
values.add("locations", locations);
values.add("locationCharsets", locationCharsets);
values.add("locationValues", StringUtils.commaDelimitedListToStringArray(locationAttr));
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {

View File

@@ -16,19 +16,14 @@
package org.springframework.web.servlet.config.annotation;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.cache.Cache;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.CacheControl;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.servlet.config.MvcNamespaceUtils;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
@@ -42,13 +37,9 @@ import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
*/
public class ResourceHandlerRegistration {
private final ResourceLoader resourceLoader;
private final String[] pathPatterns;
private final List<Resource> locations = new ArrayList<>();
private final Map<Resource, Charset> locationCharsets = new HashMap<>();
private final List<String> locationValues = new ArrayList<>();
@Nullable
private Integer cachePeriod;
@@ -62,12 +53,10 @@ public class ResourceHandlerRegistration {
/**
* Create a {@link ResourceHandlerRegistration} instance.
* @param resourceLoader a resource loader for turning a String location into a {@link Resource}
* @param pathPatterns one or more resource URL path patterns
*/
public ResourceHandlerRegistration(ResourceLoader resourceLoader, String... pathPatterns) {
public ResourceHandlerRegistration(String... pathPatterns) {
Assert.notEmpty(pathPatterns, "At least one path pattern is required for resource handling.");
this.resourceLoader = resourceLoader;
this.pathPatterns = pathPatterns;
}
@@ -91,8 +80,7 @@ public class ResourceHandlerRegistration {
* chained method invocation
*/
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
MvcNamespaceUtils.loadResourceLocations(
resourceLocations, this.resourceLoader, this.locations, this.locationCharsets);
this.locationValues.addAll(Arrays.asList(resourceLocations));
return this;
}
@@ -182,8 +170,7 @@ public class ResourceHandlerRegistration {
handler.setResourceResolvers(this.resourceChainRegistration.getResourceResolvers());
handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
}
handler.setLocations(this.locations);
handler.setLocationCharsets(this.locationCharsets);
handler.setLocationValues(this.locationValues);
if (this.cacheControl != null) {
handler.setCacheControl(this.cacheControl);
}

View File

@@ -117,8 +117,7 @@ public class ResourceHandlerRegistry {
* registered resource handler
*/
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
ResourceHandlerRegistration registration =
new ResourceHandlerRegistration(this.applicationContext, pathPatterns);
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
this.registrations.add(registration);
return registration;
}

View File

@@ -20,7 +20,6 @@ import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -32,7 +31,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
@@ -95,11 +97,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
private static final String URL_RESOURCE_CHARSET_PREFIX = "[charset=";
private final List<Resource> locations = new ArrayList<>(4);
private final Map<Resource, Charset> locationCharsets = new HashMap<>(4);
private final List<String> locationValues = new ArrayList<>(4);
private final List<ResourceResolver> resourceResolvers = new ArrayList<>(4);
private final List<ResourceTransformer> resourceTransformers = new ArrayList<>(4);
@@ -129,9 +135,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/**
* Set the {@code List} of {@code Resource} paths to use as sources
* Set the {@code List} of {@code Resource} locations to use as sources
* for serving static resources.
* @see #setLocationCharsets(Map)
* @see #setLocationValues(List)
*/
public void setLocations(List<Resource> locations) {
Assert.notNull(locations, "Locations list must not be null");
@@ -140,35 +146,27 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
}
/**
* Return the {@code List} of {@code Resource} paths to use as sources
* for serving static resources.
* Return the configured {@code List} of {@code Resource} locations.
* Note that if {@link #setLocationValues(List) locationValues} are provided,
* instead of loaded Resource-based locations, this method will return
* empty until after initialization via {@link #afterPropertiesSet()}.
*/
public List<Resource> getLocations() {
return this.locations;
}
/**
* Specify charsets associated with the configured {@link #setLocations(List)
* locations}. This is supported for
* {@link org.springframework.core.io.UrlResource URL resources} such as a
* file or an HTTP URL location and is used in {@link PathResourceResolver}
* to correctly encode paths relative to the location.
* <p><strong>Note:</strong> The charset is used only if the
* {@link #setUrlPathHelper urlPathHelper} property is also configured and
* its {@code urlDecode} property is set to {@code true}.
* An alternative to {@link #setLocations(List)} that accepts a list of
* String-based location values, with support for {@link UrlResource}'s
* (e.g. files or HTTP URLs) with a special prefix to indicate the charset
* to use when appending relative paths. For example
* {@code "[charset=Windows-31J]http://example.org/path"}.
* @since 4.3.13
*/
public void setLocationCharsets(Map<Resource, Charset> locationCharsets) {
this.locationCharsets.clear();
this.locationCharsets.putAll(locationCharsets);
}
/**
* Return charsets associated with static resource locations.
* @since 4.3.13
*/
public Map<Resource, Charset> getLocationCharsets() {
return Collections.unmodifiableMap(this.locationCharsets);
public void setLocationValues(List<String> locationValues) {
Assert.notNull(locationValues, "Location values list must not be null");
this.locationValues.clear();
this.locationValues.addAll(locationValues);
}
/**
@@ -303,6 +301,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
@Override
public void afterPropertiesSet() throws Exception {
loadResourceLocations();
if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) {
logger.warn("Locations list is empty. No resources will be served unless a " +
"custom ResourceResolver is configured as an alternative to PathResourceResolver.");
@@ -311,6 +312,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
if (this.resourceResolvers.isEmpty()) {
this.resourceResolvers.add(new PathResourceResolver());
}
initAllowedLocations();
if (this.resourceHttpMessageConverter == null) {
@@ -323,6 +325,46 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
this.contentNegotiationStrategy = initContentNegotiationStrategy();
}
private void loadResourceLocations() {
if (!CollectionUtils.isEmpty(this.locations) && !CollectionUtils.isEmpty(this.locationValues)) {
throw new IllegalArgumentException("Please set either Resource-based \"locations\" or " +
"String-based \"locationValues\", but not both.");
}
if (CollectionUtils.isEmpty(this.locationValues)) {
return;
}
ApplicationContext appContext = obtainApplicationContext();
ConfigurableBeanFactory beanFactory = null;
if (appContext.getAutowireCapableBeanFactory() instanceof ConfigurableBeanFactory) {
beanFactory = ((ConfigurableBeanFactory) appContext.getAutowireCapableBeanFactory());
}
for (String location : this.locationValues) {
if (beanFactory != null) {
location = beanFactory.resolveEmbeddedValue(location);
Assert.notNull(location, "Null location");
}
Charset charset = null;
location = location.trim();
if (location.startsWith(URL_RESOURCE_CHARSET_PREFIX)) {
int endIndex = location.indexOf("]", URL_RESOURCE_CHARSET_PREFIX.length());
if (endIndex == -1) {
throw new IllegalArgumentException("Invalid charset syntax in location: " + location);
}
String value = location.substring(URL_RESOURCE_CHARSET_PREFIX.length(), endIndex);
charset = Charset.forName(value);
location = location.substring(endIndex + 1);
}
Resource resource = appContext.getResource(location);
this.locations.add(resource);
if (charset != null) {
if (!(resource instanceof UrlResource)) {
throw new IllegalArgumentException("Unexpected charset for non-UrlResource: " + resource);
}
this.locationCharsets.put(resource, charset);
}
}
}
/**
* Look for a {@code PathResourceResolver} among the configured resource
* resolvers and set its {@code allowedLocations} property (if empty) to