Skip to content

Commit 9470719

Browse files
committed
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.
1 parent 97bc276 commit 9470719

File tree

13 files changed

+396
-36
lines changed

13 files changed

+396
-36
lines changed

spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ public void setUrlDecode(boolean urlDecode) {
9696
this.urlDecode = urlDecode;
9797
}
9898

99+
/**
100+
* Whether to decode the request URI when determining the lookup path.
101+
* @since 4.3.13
102+
*/
103+
public boolean isUrlDecode() {
104+
return this.urlDecode;
105+
}
106+
99107
/**
100108
* Set if ";" (semicolon) content should be stripped from the request URI.
101109
* <p>Default is "true".

spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,21 @@
1616

1717
package org.springframework.web.servlet.config;
1818

19+
import java.nio.charset.Charset;
1920
import java.util.LinkedHashMap;
21+
import java.util.List;
2022
import java.util.Map;
2123

2224
import org.springframework.beans.factory.config.BeanDefinition;
2325
import org.springframework.beans.factory.config.RuntimeBeanReference;
2426
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
2527
import org.springframework.beans.factory.support.RootBeanDefinition;
2628
import org.springframework.beans.factory.xml.ParserContext;
29+
import org.springframework.core.io.Resource;
30+
import org.springframework.core.io.ResourceLoader;
31+
import org.springframework.core.io.UrlResource;
2732
import org.springframework.util.AntPathMatcher;
33+
import org.springframework.util.Assert;
2834
import org.springframework.util.PathMatcher;
2935
import org.springframework.web.cors.CorsConfiguration;
3036
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
@@ -40,7 +46,7 @@
4046
* @author Brian Clozel
4147
* @since 3.1
4248
*/
43-
abstract class MvcNamespaceUtils {
49+
public abstract class MvcNamespaceUtils {
4450

4551
private static final String BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME =
4652
BeanNameUrlHandlerMapping.class.getName();
@@ -59,6 +65,8 @@ abstract class MvcNamespaceUtils {
5965

6066
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
6167

68+
private static final String URL_RESOURCE_CHARSET_PREFIX = "[charset=";
69+
6270

6371
public static void registerDefaultComponents(ParserContext parserContext, Object source) {
6472
registerBeanNameUrlHandlerMapping(parserContext, source);
@@ -221,5 +229,37 @@ public static Object getContentNegotiationManager(ParserContext context) {
221229
return null;
222230
}
223231

232+
/**
233+
* Load the {@link Resource}'s for the given locations with the given
234+
* {@link ResourceLoader} and add them to the output list. Also for
235+
* {@link org.springframework.core.io.UrlResource URL-based resources} (e.g.
236+
* files, HTTP URLs, etc) this method supports a special prefix to indicate
237+
* the charset associated with the URL so that relative paths appended to it
238+
* can be encoded correctly, e.g.
239+
* {@code [charset=Windows-31J]http://example.org/path}. The charsets, if
240+
* any, are added to the output map.
241+
* @since 4.3.13
242+
*/
243+
public static void loadResourceLocations(String[] locations, ResourceLoader resourceLoader,
244+
List<Resource> outputLocations, Map<Resource, Charset> outputLocationCharsets) {
245+
246+
for (String location : locations) {
247+
Charset charset = null;
248+
location = location.trim();
249+
if (location.startsWith(URL_RESOURCE_CHARSET_PREFIX)) {
250+
int endIndex = location.indexOf("]", URL_RESOURCE_CHARSET_PREFIX.length());
251+
Assert.isTrue(endIndex != -1, "Invalid charset syntax in location: " + location);
252+
String value = location.substring(URL_RESOURCE_CHARSET_PREFIX.length(), endIndex);
253+
charset = Charset.forName(value);
254+
location = location.substring(endIndex + 1);
255+
}
256+
Resource resource = resourceLoader.getResource(location);
257+
outputLocations.add(resource);
258+
if (charset != null) {
259+
Assert.isInstanceOf(UrlResource.class, resource, "Unexpected charset for: " + resource);
260+
outputLocationCharsets.put(resource, charset);
261+
}
262+
}
263+
}
224264

225265
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package org.springframework.web.servlet.config;
1818

19+
import java.nio.charset.Charset;
20+
import java.util.ArrayList;
1921
import java.util.Arrays;
22+
import java.util.HashMap;
23+
import java.util.List;
2024
import java.util.Map;
2125
import java.util.concurrent.TimeUnit;
2226

@@ -34,6 +38,8 @@
3438
import org.springframework.beans.factory.xml.ParserContext;
3539
import org.springframework.cache.concurrent.ConcurrentMapCache;
3640
import org.springframework.core.Ordered;
41+
import org.springframework.core.io.Resource;
42+
import org.springframework.core.io.ResourceLoader;
3743
import org.springframework.util.ClassUtils;
3844
import org.springframework.util.StringUtils;
3945
import org.springframework.util.xml.DomUtils;
@@ -91,7 +97,10 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
9197

9298
registerUrlProvider(parserContext, source);
9399

94-
String resourceHandlerName = registerResourceHandler(parserContext, element, source);
100+
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, parserContext, source);
101+
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, parserContext, source);
102+
103+
String resourceHandlerName = registerResourceHandler(parserContext, element, pathHelperRef, source);
95104
if (resourceHandlerName == null) {
96105
return null;
97106
}
@@ -104,9 +113,6 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
104113
}
105114
urlMap.put(resourceRequestPath, resourceHandlerName);
106115

107-
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, parserContext, source);
108-
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, parserContext, source);
109-
110116
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
111117
handlerMappingDef.setSource(source);
112118
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
@@ -153,22 +159,37 @@ private void registerUrlProvider(ParserContext parserContext, Object source) {
153159
}
154160
}
155161

156-
private String registerResourceHandler(ParserContext parserContext, Element element, Object source) {
162+
private String registerResourceHandler(ParserContext parserContext, Element element,
163+
RuntimeBeanReference pathHelperRef, Object source) {
164+
157165
String locationAttr = element.getAttribute("location");
158166
if (!StringUtils.hasText(locationAttr)) {
159167
parserContext.getReaderContext().error("The 'location' attribute is required.", parserContext.extractSource(element));
160168
return null;
161169
}
162170

163-
ManagedList<String> locations = new ManagedList<String>();
164-
locations.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(locationAttr)));
171+
String[] locationValues = StringUtils.commaDelimitedListToStringArray(locationAttr);
172+
ManagedList<Object> locations = new ManagedList<Object>();
173+
Map<Resource, Charset> locationCharsets = new HashMap<Resource, Charset>();
174+
ResourceLoader resourceLoader = parserContext.getReaderContext().getResourceLoader();
175+
176+
if (resourceLoader != null) {
177+
List<Resource> resources = new ArrayList<Resource>();
178+
MvcNamespaceUtils.loadResourceLocations(locationValues, resourceLoader, resources, locationCharsets);
179+
locations.addAll(resources);
180+
}
181+
else {
182+
locations.addAll(Arrays.asList(locationValues));
183+
}
165184

166185
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
167186
resourceHandlerDef.setSource(source);
168187
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
169188

170189
MutablePropertyValues values = resourceHandlerDef.getPropertyValues();
190+
values.add("urlPathHelper", pathHelperRef);
171191
values.add("locations", locations);
192+
values.add("locationCharsets", locationCharsets);
172193

173194
String cacheSeconds = element.getAttribute("cache-period");
174195
if (StringUtils.hasText(cacheSeconds)) {

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@
1616

1717
package org.springframework.web.servlet.config.annotation;
1818

19+
import java.nio.charset.Charset;
1920
import java.util.ArrayList;
21+
import java.util.HashMap;
2022
import java.util.List;
23+
import java.util.Map;
2124

2225
import org.springframework.cache.Cache;
2326
import org.springframework.core.io.Resource;
2427
import org.springframework.core.io.ResourceLoader;
25-
import org.springframework.util.Assert;
2628
import org.springframework.http.CacheControl;
29+
import org.springframework.util.Assert;
30+
import org.springframework.web.servlet.config.MvcNamespaceUtils;
2731
import org.springframework.web.servlet.resource.PathResourceResolver;
2832
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
2933

@@ -43,6 +47,8 @@ public class ResourceHandlerRegistration {
4347

4448
private final List<Resource> locations = new ArrayList<Resource>();
4549

50+
private final Map<Resource, Charset> locationCharsets = new HashMap<Resource, Charset>();
51+
4652
private Integer cachePeriod;
4753

4854
private CacheControl cacheControl;
@@ -61,20 +67,27 @@ public ResourceHandlerRegistration(ResourceLoader resourceLoader, String... path
6167
this.pathPatterns = pathPatterns;
6268
}
6369

64-
6570
/**
66-
* Add one or more resource locations from which to serve static content. Each location must point to a valid
67-
* directory. Multiple locations may be specified as a comma-separated list, and the locations will be checked
71+
* Add one or more resource locations from which to serve static content.
72+
* Each location must point to a valid directory. Multiple locations may
73+
* be specified as a comma-separated list, and the locations will be checked
6874
* for a given resource in the order specified.
69-
* <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} allows resources to
70-
* be served both from the web application root and from any JAR on the classpath that contains a
71-
* {@code /META-INF/public-web-resources/} directory, with resources in the web application root taking precedence.
72-
* @return the same {@link ResourceHandlerRegistration} instance, for chained method invocation
75+
* <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}}
76+
* allows resources to be served both from the web application root and
77+
* from any JAR on the classpath that contains a
78+
* {@code /META-INF/public-web-resources/} directory, with resources in the
79+
* web application root taking precedence.
80+
* <p>For {@link org.springframework.core.io.UrlResource URL-based resources}
81+
* (e.g. files, HTTP URLs, etc) this method supports a special prefix to
82+
* indicate the charset associated with the URL so that relative paths
83+
* appended to it can be encoded correctly, e.g.
84+
* {@code [charset=Windows-31J]http://example.org/path}.
85+
* @return the same {@link ResourceHandlerRegistration} instance, for
86+
* chained method invocation
7387
*/
7488
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
75-
for (String location : resourceLocations) {
76-
this.locations.add(resourceLoader.getResource(location));
77-
}
89+
MvcNamespaceUtils.loadResourceLocations(
90+
resourceLocations, this.resourceLoader, this.locations, this.locationCharsets);
7891
return this;
7992
}
8093

@@ -165,6 +178,7 @@ protected ResourceHttpRequestHandler getRequestHandler() {
165178
handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
166179
}
167180
handler.setLocations(this.locations);
181+
handler.setLocationCharsets(this.locationCharsets);
168182
if (this.cacheControl != null) {
169183
handler.setCacheControl(this.cacheControl);
170184
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
3333
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
3434
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
35+
import org.springframework.web.util.UrlPathHelper;
3536

3637
/**
3738
* Stores registrations of resource handlers for serving static resources such as images, css files and others
@@ -57,6 +58,8 @@ public class ResourceHandlerRegistry {
5758

5859
private final ContentNegotiationManager contentNegotiationManager;
5960

61+
private final UrlPathHelper pathHelper;
62+
6063
private final List<ResourceHandlerRegistration> registrations = new ArrayList<ResourceHandlerRegistration>();
6164

6265
private int order = Integer.MAX_VALUE -1;
@@ -81,10 +84,24 @@ public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletCon
8184
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
8285
ContentNegotiationManager contentNegotiationManager) {
8386

87+
this(applicationContext, servletContext, contentNegotiationManager, null);
88+
}
89+
90+
/**
91+
* A variant of
92+
* {@link #ResourceHandlerRegistry(ApplicationContext, ServletContext, ContentNegotiationManager)}
93+
* that also accepts the {@link UrlPathHelper} used for mapping requests
94+
* to static resources.
95+
* @since 4.3.13
96+
*/
97+
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
98+
ContentNegotiationManager contentNegotiationManager, UrlPathHelper pathHelper) {
99+
84100
Assert.notNull(applicationContext, "ApplicationContext is required");
85101
this.applicationContext = applicationContext;
86102
this.servletContext = servletContext;
87103
this.contentNegotiationManager = contentNegotiationManager;
104+
this.pathHelper = pathHelper;
88105
}
89106

90107

@@ -140,9 +157,14 @@ protected AbstractHandlerMapping getHandlerMapping() {
140157
for (ResourceHandlerRegistration registration : this.registrations) {
141158
for (String pathPattern : registration.getPathPatterns()) {
142159
ResourceHttpRequestHandler handler = registration.getRequestHandler();
160+
if (this.pathHelper != null) {
161+
handler.setUrlPathHelper(this.pathHelper);
162+
}
163+
if (this.contentNegotiationManager != null) {
164+
handler.setContentNegotiationManager(this.contentNegotiationManager);
165+
}
143166
handler.setServletContext(this.servletContext);
144167
handler.setApplicationContext(this.applicationContext);
145-
handler.setContentNegotiationManager(this.contentNegotiationManager);
146168
try {
147169
handler.afterPropertiesSet();
148170
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
5555
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
5656
import org.springframework.util.AntPathMatcher;
57+
import org.springframework.util.Assert;
5758
import org.springframework.util.ClassUtils;
5859
import org.springframework.util.PathMatcher;
5960
import org.springframework.validation.Errors;
@@ -439,8 +440,11 @@ public BeanNameUrlHandlerMapping beanNameHandlerMapping() {
439440
*/
440441
@Bean
441442
public HandlerMapping resourceHandlerMapping() {
443+
Assert.state(this.applicationContext != null, "No ApplicationContext set");
444+
Assert.state(this.servletContext != null, "No ServletContext set");
445+
442446
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
443-
this.servletContext, mvcContentNegotiationManager());
447+
this.servletContext, mvcContentNegotiationManager(), mvcUrlPathHelper());
444448
addResourceHandlers(registry);
445449

446450
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();

0 commit comments

Comments
 (0)