提交 369d33c3 编写于 作者: R Rossen Stoyanchev

Support charset config by (static) resource location

This commit adds support for configuring static resource locations
with a charset to be applied to relative paths.
上级 db006691
......@@ -104,6 +104,14 @@ public class UrlPathHelper {
this.urlDecode = urlDecode;
}
/**
* Whether to decode the request URI when determining the lookup path.
* @since 4.3.13
*/
public boolean isUrlDecode() {
return this.urlDecode;
}
/**
* Set if ";" (semicolon) content should be stripped from the request URI.
* <p>Default is "true".
......
......@@ -16,7 +16,9 @@
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;
......@@ -24,8 +26,12 @@ 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.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
......@@ -41,7 +47,7 @@ import org.springframework.web.util.UrlPathHelper;
* @author Brian Clozel
* @since 3.1
*/
abstract class MvcNamespaceUtils {
public abstract class MvcNamespaceUtils {
private static final String BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME =
BeanNameUrlHandlerMapping.class.getName();
......@@ -60,6 +66,8 @@ 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);
......@@ -224,5 +232,37 @@ 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());
Assert.isTrue(endIndex != -1, "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) {
Assert.isInstanceOf(UrlResource.class, resource, "Unexpected charset for: " + resource);
outputLocationCharsets.put(resource, charset);
}
}
}
}
......@@ -16,7 +16,11 @@
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;
......@@ -34,6 +38,8 @@ 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;
......@@ -92,7 +98,10 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
registerUrlProvider(context, source);
String resourceHandlerName = registerResourceHandler(context, element, source);
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, context, source);
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, context, source);
String resourceHandlerName = registerResourceHandler(context, element, pathHelperRef, source);
if (resourceHandlerName == null) {
return null;
}
......@@ -105,9 +114,6 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
}
urlMap.put(resourceRequestPath, resourceHandlerName);
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, context, source);
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, context, source);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
......@@ -156,7 +162,9 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
}
@Nullable
private String registerResourceHandler(ParserContext context, Element element, @Nullable Object source) {
private String registerResourceHandler(ParserContext context, Element element,
RuntimeBeanReference pathHelperRef, @Nullable Object source) {
String locationAttr = element.getAttribute("location");
if (!StringUtils.hasText(locationAttr)) {
String message = "The 'location' attribute is required.";
......@@ -164,15 +172,28 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
return null;
}
ManagedList<String> locations = new ManagedList<>();
locations.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(locationAttr)));
String[] locationValues = StringUtils.commaDelimitedListToStringArray(locationAttr);
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);
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {
......
......@@ -16,8 +16,11 @@
package org.springframework.web.servlet.config.annotation;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.cache.Cache;
import org.springframework.core.io.Resource;
......@@ -25,6 +28,7 @@ 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;
......@@ -44,6 +48,8 @@ public class ResourceHandlerRegistration {
private final List<Resource> locations = new ArrayList<>();
private final Map<Resource, Charset> locationCharsets = new HashMap<>();
@Nullable
private Integer cachePeriod;
......@@ -67,18 +73,26 @@ public class ResourceHandlerRegistration {
/**
* Add one or more resource locations from which to serve static content. Each location must point to a valid
* directory. Multiple locations may be specified as a comma-separated list, and the locations will be checked
* Add one or more resource locations from which to serve static content.
* Each location must point to a valid directory. Multiple locations may
* be specified as a comma-separated list, and the locations will be checked
* for a given resource in the order specified.
* <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} allows resources to
* be served both from the web application root and from any JAR on the classpath that contains a
* {@code /META-INF/public-web-resources/} directory, with resources in the web application root taking precedence.
* @return the same {@link ResourceHandlerRegistration} instance, for chained method invocation
* <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}}
* allows resources to be served both from the web application root and
* from any JAR on the classpath that contains a
* {@code /META-INF/public-web-resources/} directory, with resources in the
* web application root taking precedence.
* <p>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}.
* @return the same {@link ResourceHandlerRegistration} instance, for
* chained method invocation
*/
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
for (String location : resourceLocations) {
this.locations.add(resourceLoader.getResource(location));
}
MvcNamespaceUtils.loadResourceLocations(
resourceLocations, this.resourceLoader, this.locations, this.locationCharsets);
return this;
}
......@@ -169,6 +183,7 @@ public class ResourceHandlerRegistration {
handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
}
handler.setLocations(this.locations);
handler.setLocationCharsets(this.locationCharsets);
if (this.cacheControl != null) {
handler.setCacheControl(this.cacheControl);
}
......
......@@ -33,6 +33,7 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.UrlPathHelper;
/**
* Stores registrations of resource handlers for serving static resources such as images, css files and others
......@@ -59,6 +60,9 @@ public class ResourceHandlerRegistry {
@Nullable
private final ContentNegotiationManager contentNegotiationManager;
@Nullable
private final UrlPathHelper pathHelper;
private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();
private int order = Integer.MAX_VALUE -1;
......@@ -83,10 +87,24 @@ public class ResourceHandlerRegistry {
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
@Nullable ContentNegotiationManager contentNegotiationManager) {
this(applicationContext, servletContext, contentNegotiationManager, null);
}
/**
* A variant of
* {@link #ResourceHandlerRegistry(ApplicationContext, ServletContext, ContentNegotiationManager)}
* that also accepts the {@link UrlPathHelper} used for mapping requests
* to static resources.
* @since 4.3.13
*/
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
ContentNegotiationManager contentNegotiationManager, @Nullable UrlPathHelper pathHelper) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.applicationContext = applicationContext;
this.servletContext = servletContext;
this.contentNegotiationManager = contentNegotiationManager;
this.pathHelper = pathHelper;
}
......@@ -143,6 +161,9 @@ public class ResourceHandlerRegistry {
for (ResourceHandlerRegistration registration : this.registrations) {
for (String pathPattern : registration.getPathPatterns()) {
ResourceHttpRequestHandler handler = registration.getRequestHandler();
if (this.pathHelper != null) {
handler.setUrlPathHelper(this.pathHelper);
}
if (this.contentNegotiationManager != null) {
handler.setContentNegotiationManager(this.contentNegotiationManager);
}
......
......@@ -481,7 +481,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
Assert.state(this.servletContext != null, "No ServletContext set");
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, mvcContentNegotiationManager());
this.servletContext, mvcContentNegotiationManager(), mvcUrlPathHelper());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
......
......@@ -18,8 +18,14 @@ package org.springframework.web.servlet.resource;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.ClassPathResource;
......@@ -28,6 +34,8 @@ import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.UrlPathHelper;
/**
* A simple {@code ResourceResolver} that tries to find a resource under the given
......@@ -46,6 +54,11 @@ public class PathResourceResolver extends AbstractResourceResolver {
@Nullable
private Resource[] allowedLocations;
private final Map<Resource, Charset> locationCharsets = new HashMap<>(4);
@Nullable
private UrlPathHelper urlPathHelper;
/**
* By default when a Resource is found, the path of the resolved resource is
......@@ -73,29 +86,75 @@ public class PathResourceResolver extends AbstractResourceResolver {
return this.allowedLocations;
}
/**
* Configure charsets associated with locations. If a static resource is found
* under a {@link org.springframework.core.io.UrlResource URL resource}
* location the charset is used to encode the relative path
* <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 true.
* @param locationCharsets charsets by location
* @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(locationCharsets);
}
/**
* Provide a reference to the {@link UrlPathHelper} used to map requests to
* static resources. This helps to derive information about the lookup path
* such as whether it is decoded or not.
* @param urlPathHelper a reference to the path helper
* @since 4.3.13
*/
public void setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
}
/**
* The configured {@link UrlPathHelper}.
* @since 4.3.13
*/
@Nullable
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
@Override
protected Resource resolveResourceInternal(@Nullable HttpServletRequest request, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
return getResource(requestPath, locations);
return getResource(requestPath, request, locations);
}
@Override
protected String resolveUrlPathInternal(String resourcePath, List<? extends Resource> locations,
ResourceResolverChain chain) {
return (StringUtils.hasText(resourcePath) && getResource(resourcePath, locations) != null ? resourcePath : null);
return (StringUtils.hasText(resourcePath) &&
getResource(resourcePath, null, locations) != null ? resourcePath : null);
}
@Nullable
private Resource getResource(String resourcePath, List<? extends Resource> locations) {
private Resource getResource(String resourcePath, @Nullable HttpServletRequest request,
List<? extends Resource> locations) {
for (Resource location : locations) {
try {
if (logger.isTraceEnabled()) {
logger.trace("Checking location: " + location);
}
Resource resource = getResource(resourcePath, location);
String pathToUse = encodeIfNecessary(resourcePath, request, location);
Resource resource = getResource(pathToUse, location);
if (resource != null) {
if (logger.isTraceEnabled()) {
logger.trace("Found match: " + resource);
......@@ -210,4 +269,29 @@ public class PathResourceResolver extends AbstractResourceResolver {
return true;
}
private String encodeIfNecessary(String path, @Nullable HttpServletRequest request, Resource location) {
if (shouldEncodeRelativePath(location) && request != null) {
Charset charset = this.locationCharsets.getOrDefault(location, StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder();
StringTokenizer tokenizer = new StringTokenizer(path, "/");
while (tokenizer.hasMoreTokens()) {
String value = UriUtils.encode(tokenizer.nextToken(), charset);
sb.append(value);
sb.append("/");
}
if (!path.endsWith("/")) {
sb.setLength(sb.length() - 1);
}
return sb.toString();
}
else {
return path;
}
}
private boolean shouldEncodeRelativePath(Resource location) {
return location instanceof UrlResource &&
this.urlPathHelper != null && this.urlPathHelper.isUrlDecode();
}
}
......@@ -18,7 +18,9 @@ package org.springframework.web.servlet.resource;
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;
......@@ -31,7 +33,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
......@@ -55,6 +56,7 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.UrlPathHelper;
/**
* {@code HttpRequestHandler} that serves static resources in an optimized way
......@@ -96,6 +98,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
private final List<Resource> locations = new ArrayList<>(4);
private final Map<Resource, Charset> locationCharsets = new HashMap<>(4);
private final List<ResourceResolver> resourceResolvers = new ArrayList<>(4);
private final List<ResourceTransformer> resourceTransformers = new ArrayList<>(4);
......@@ -115,6 +119,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
@Nullable
private CorsConfiguration corsConfiguration;
@Nullable
private UrlPathHelper urlPathHelper;
public ResourceHttpRequestHandler() {
super(HttpMethod.GET.name(), HttpMethod.HEAD.name());
......@@ -124,6 +131,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/**
* Set the {@code List} of {@code Resource} paths to use as sources
* for serving static resources.
* @see #setLocationCharsets(Map)
*/
public void setLocations(List<Resource> locations) {
Assert.notNull(locations, "Locations list must not be null");
......@@ -139,6 +147,31 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
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 true.
* @param locationCharsets charsets by location
* @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(locationCharsets);
}
/**
* Configure the list of {@link ResourceResolver}s to use.
* <p>By default {@link PathResourceResolver} is configured. If using this property,
......@@ -249,6 +282,26 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return this.corsConfiguration;
}
/**
* Provide a reference to the {@link UrlPathHelper} used to map requests to
* static resources. This helps to derive information about the lookup path
* such as whether it is decoded or not.
* @param urlPathHelper a reference to the path helper
* @since 4.3.13
*/
public void setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
}
/**
* The configured {@link UrlPathHelper}.
* @since 4.3.13
*/
@Nullable
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
@Override
public void afterPropertiesSet() throws Exception {
......@@ -287,6 +340,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
pathResolver.setAllowedLocations(getLocations().toArray(new Resource[getLocations().size()]));
}
if (this.urlPathHelper != null) {
pathResolver.setLocationCharsets(this.locationCharsets);
pathResolver.setUrlPathHelper(this.urlPathHelper);
}
break;
}
}
......
......@@ -639,6 +639,9 @@
"/, classpath:/META-INF/public-web-resources/" will allow resources to be served both from the web app
root and from any JAR on the classpath that contains a /META-INF/public-web-resources/ directory,
with resources in the web app root taking precedence.
For URL-based resources (e.g. files, HTTP URLs, etc) this property 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. "[charset=Windows-31J]http://example.org/path".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
......
......@@ -20,7 +20,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
......@@ -418,10 +417,14 @@ public class MvcNamespaceTests {
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertNotNull(mapping.getUrlMap().get("/resources/**"));
ResourceHttpRequestHandler handler = appContext.getBean((String) mapping.getUrlMap().get("/resources/**"),
ResourceHttpRequestHandler.class);
String beanName = (String) mapping.getUrlMap().get("/resources/**");
ResourceHttpRequestHandler handler = appContext.getBean(beanName, ResourceHttpRequestHandler.class);
assertNotNull(handler);
assertNotNull(handler.getUrlPathHelper());
assertEquals(1, handler.getLocationCharsets().size());
assertEquals(StandardCharsets.ISO_8859_1, handler.getLocationCharsets().values().iterator().next());
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.instanceOf(CachingResourceResolver.class));
......@@ -439,6 +442,10 @@ public class MvcNamespaceTests {
assertThat(versionResolver.getStrategyMap().get("/**"),
Matchers.instanceOf(ContentVersionStrategy.class));
PathResourceResolver pathResolver = (PathResourceResolver) resolvers.get(3);
assertEquals(1, pathResolver.getLocationCharsets().size());
assertEquals(StandardCharsets.ISO_8859_1, handler.getLocationCharsets().values().iterator().next());
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(3));
assertThat(transformers.get(0), Matchers.instanceOf(CachingResourceTransformer.class));
......
......@@ -16,6 +16,7 @@
package org.springframework.web.servlet.config.annotation;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.hamcrest.Matchers;
......@@ -24,10 +25,12 @@ import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.io.UrlResource;
import org.springframework.http.CacheControl;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.http.CacheControl;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
......@@ -41,8 +44,14 @@ import org.springframework.web.servlet.resource.ResourceResolver;
import org.springframework.web.servlet.resource.ResourceTransformer;
import org.springframework.web.servlet.resource.VersionResourceResolver;
import org.springframework.web.servlet.resource.WebJarsResourceResolver;
import org.springframework.web.util.UrlPathHelper;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ResourceHandlerRegistry}.
......@@ -60,8 +69,10 @@ public class ResourceHandlerRegistryTests {
@Before
public void setUp() {
this.registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(), new MockServletContext());
this.registration = registry.addResourceHandler("/resources/**");
this.registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(),
new MockServletContext(), new ContentNegotiationManager(), new UrlPathHelper());
this.registration = this.registry.addResourceHandler("/resources/**");
this.registration.addResourceLocations("classpath:org/springframework/web/servlet/config/annotation/");
this.response = new MockHttpServletResponse();
}
......@@ -211,9 +222,27 @@ public class ResourceHandlerRegistryTests {
assertThat(transformers.get(2), Matchers.sameInstance(cssLinkTransformer));
}
@Test
public void urlResourceWithCharset() throws Exception {
this.registration.addResourceLocations("[charset=ISO-8859-1]file:///tmp");
this.registration.resourceChain(true);
ResourceHttpRequestHandler handler = getHandler("/resources/**");
UrlResource resource = (UrlResource) handler.getLocations().get(1);
assertEquals("file:/tmp", resource.getURL().toString());
assertNotNull(handler.getUrlPathHelper());
assertEquals(1, handler.getLocationCharsets().size());
assertEquals(StandardCharsets.ISO_8859_1, handler.getLocationCharsets().get(resource));
List<ResourceResolver> resolvers = handler.getResourceResolvers();
PathResourceResolver resolver = (PathResourceResolver) resolvers.get(resolvers.size()-1);
assertEquals(1, resolver.getLocationCharsets().size());
assertEquals(StandardCharsets.ISO_8859_1, handler.getLocationCharsets().values().iterator().next());
}
private ResourceHttpRequestHandler getHandler(String pathPattern) {
SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
return (ResourceHttpRequestHandler) handlerMapping.getUrlMap().get(pathPattern);
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
return (ResourceHttpRequestHandler) hm.getUrlMap().get(pathPattern);
}
}
......@@ -16,17 +16,28 @@
package org.springframework.web.servlet.resource;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.util.UrlPathHelper;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Unit tests for
......@@ -132,4 +143,52 @@ public class PathResourceResolverTests {
assertNull(path);
}
@Test
public void relativePathEncodedForUrlResource() throws Exception {
TestUrlResource location = new TestUrlResource("file:///tmp");
List<TestUrlResource> locations = Collections.singletonList(location);
// ISO-8859-1
this.resolver.setUrlPathHelper(new UrlPathHelper());
this.resolver.setLocationCharsets(Collections.singletonMap(location, StandardCharsets.ISO_8859_1));
this.resolver.resolveResource(new MockHttpServletRequest(), "/Ä ;ä.txt", locations, null);
assertEquals("%C4%20%3B%E4.txt", location.getSavedRelativePath());
// UTF-8
this.resolver.setLocationCharsets(Collections.singletonMap(location, StandardCharsets.UTF_8));
this.resolver.resolveResource(new MockHttpServletRequest(), "/Ä ;ä.txt", locations, null);
assertEquals("%C3%84%20%3B%C3%A4.txt", location.getSavedRelativePath());
// UTF-8 by default
this.resolver.setLocationCharsets(Collections.emptyMap());
this.resolver.resolveResource(new MockHttpServletRequest(), "/Ä ;ä.txt", locations, null);
assertEquals("%C3%84%20%3B%C3%A4.txt", location.getSavedRelativePath());
}
private static class TestUrlResource extends UrlResource {
private String relativePath;
public TestUrlResource(String path) throws MalformedURLException {
super(path);
}
public String getSavedRelativePath() {
return this.relativePath;
}
@Override
public Resource createRelative(String relativePath) throws MalformedURLException {
this.relativePath = relativePath;
return this;
}
}
}
......@@ -21,7 +21,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/">
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/, [charset=ISO-8859-1]file:///tmp">
<mvc:resource-chain resource-cache="true" cache-manager="resourceCache" cache-name="test-resource-cache">
<mvc:resolvers>
<mvc:version-resolver>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册