Transform absolute links in ResourceTransformers
Prior to this change, ResourceTransformers that transformed resources by updating the links to other resources, worked only if links were relative to the resource being transformed. For example, when the CssLinkResourceTransformer rewrote links within a "main.css" resource, only links such as "../css/other.css" were rewritten. Using relative links is a recommended approach, because it's totally independent from the application servlet path, context path, mappings... This change allows absolute links to be rewritten by those Transformers, provided those links are accurate and point to existing resources. Issue: SPR-12137
This commit is contained in:
parent
06f1f495c2
commit
125ae99035
|
@ -58,7 +58,7 @@ import org.springframework.util.StringUtils;
|
|||
* applications spec</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
public class AppCacheManifestTransformer implements ResourceTransformer {
|
||||
public class AppCacheManifestTransformer extends ResourceTransformerSupport {
|
||||
|
||||
private static final String MANIFEST_HEADER = "CACHE MANIFEST";
|
||||
|
||||
|
@ -129,7 +129,7 @@ public class AppCacheManifestTransformer implements ResourceTransformer {
|
|||
hashBuilder.appendString(line);
|
||||
}
|
||||
else {
|
||||
contentWriter.write(currentTransformer.transform(line, hashBuilder, resource, transformerChain) + "\n");
|
||||
contentWriter.write(currentTransformer.transform(line, hashBuilder, resource, transformerChain, request) + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,14 +151,14 @@ public class AppCacheManifestTransformer implements ResourceTransformer {
|
|||
* for the current manifest section (CACHE, NETWORK, FALLBACK, etc).
|
||||
*/
|
||||
String transform(String line, HashBuilder builder, Resource resource,
|
||||
ResourceTransformerChain transformerChain) throws IOException;
|
||||
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException;
|
||||
}
|
||||
|
||||
|
||||
private static class NoOpSection implements SectionTransformer {
|
||||
|
||||
public String transform(String line, HashBuilder builder, Resource resource, ResourceTransformerChain transformerChain)
|
||||
throws IOException {
|
||||
public String transform(String line, HashBuilder builder, Resource resource,
|
||||
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException {
|
||||
|
||||
builder.appendString(line);
|
||||
return line;
|
||||
|
@ -166,17 +166,18 @@ public class AppCacheManifestTransformer implements ResourceTransformer {
|
|||
}
|
||||
|
||||
|
||||
private static class CacheSection implements SectionTransformer {
|
||||
private class CacheSection implements SectionTransformer {
|
||||
|
||||
private final String COMMENT_DIRECTIVE = "#";
|
||||
|
||||
@Override
|
||||
public String transform(String line, HashBuilder builder,
|
||||
Resource resource, ResourceTransformerChain transformerChain) throws IOException {
|
||||
public String transform(String line, HashBuilder builder, Resource resource,
|
||||
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException {
|
||||
|
||||
if (isLink(line) && !hasScheme(line)) {
|
||||
Resource appCacheResource = transformerChain.getResolverChain().resolveResource(null, line, Arrays.asList(resource));
|
||||
String path = transformerChain.getResolverChain().resolveUrlPath(line, Arrays.asList(resource));
|
||||
ResourceResolverChain resolverChain = transformerChain.getResolverChain();
|
||||
Resource appCacheResource = resolverChain.resolveResource(null, line, Arrays.asList(resource));
|
||||
String path = resolveUrlPath(line, request, resource, transformerChain);
|
||||
builder.appendResource(appCacheResource);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Link modified: " + path + " (original: " + line + ")");
|
||||
|
|
|
@ -27,7 +27,6 @@ import java.io.IOException;
|
|||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -47,7 +46,7 @@ import java.util.Set;
|
|||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CssLinkResourceTransformer implements ResourceTransformer {
|
||||
public class CssLinkResourceTransformer extends ResourceTransformerSupport {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(CssLinkResourceTransformer.class);
|
||||
|
||||
|
@ -103,7 +102,7 @@ public class CssLinkResourceTransformer implements ResourceTransformer {
|
|||
String link = content.substring(info.getStart(), info.getEnd());
|
||||
String newLink = null;
|
||||
if (!hasScheme(link)) {
|
||||
newLink = transformerChain.getResolverChain().resolveUrlPath(link, Arrays.asList(resource));
|
||||
newLink = resolveUrlPath(link, request, resource, transformerChain);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
if (newLink != null && !link.equals(newLink)) {
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.servlet.resource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A base class for a {@code ResourceTransformer} with an optional helper method
|
||||
* for resolving public links within a transformed resource.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public abstract class ResourceTransformerSupport implements ResourceTransformer {
|
||||
|
||||
private ResourceUrlProvider resourceUrlProvider;
|
||||
|
||||
|
||||
/**
|
||||
* Configure a {@link ResourceUrlProvider} to use when resolving the public
|
||||
* URL of links in a transformed resource (e.g. import links in a CSS file).
|
||||
* This is required only for links expressed as full paths, i.e. including
|
||||
* context and servlet path, and not for relative links.
|
||||
*
|
||||
* <p>By default this property is not set. In that case if a
|
||||
* {@code ResourceUrlProvider} is needed an attempt is made to find the
|
||||
* {@code ResourceUrlProvider} exposed through the
|
||||
* {@link org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor
|
||||
* ResourceUrlProviderExposingInterceptor} (configured by default in the MVC
|
||||
* Java config and XML namespace). Therefore explicitly configuring this
|
||||
* property should not be needed in most cases.
|
||||
* @param resourceUrlProvider the URL provider to use
|
||||
*/
|
||||
public void setResourceUrlProvider(ResourceUrlProvider resourceUrlProvider) {
|
||||
this.resourceUrlProvider = resourceUrlProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the configured {@code ResourceUrlProvider}.
|
||||
*/
|
||||
public ResourceUrlProvider getResourceUrlProvider() {
|
||||
return this.resourceUrlProvider;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A transformer can use this method when a resource being transformed
|
||||
* contains links to other resources. Such links need to be replaced with the
|
||||
* public facing link as determined by the resource resolver chain (e.g. the
|
||||
* public URL may have a version inserted).
|
||||
*
|
||||
* @param resourcePath the path to a resource that needs to be re-written
|
||||
* @param request the current request
|
||||
* @param resource the resource being transformed
|
||||
* @param transformerChain the transformer chain
|
||||
* @return the resolved URL or null
|
||||
*/
|
||||
protected String resolveUrlPath(String resourcePath, HttpServletRequest request,
|
||||
Resource resource, ResourceTransformerChain transformerChain) {
|
||||
|
||||
if (!resourcePath.startsWith("/")) {
|
||||
// try resolving as relative path
|
||||
return transformerChain.getResolverChain().resolveUrlPath(resourcePath, Arrays.asList(resource));
|
||||
}
|
||||
else {
|
||||
// full resource path
|
||||
ResourceUrlProvider urlProvider = findResourceUrlProvider(request);
|
||||
return (urlProvider != null ? urlProvider.getForRequestUrl(request, resourcePath) : null);
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceUrlProvider findResourceUrlProvider(HttpServletRequest request) {
|
||||
if (this.resourceUrlProvider != null) {
|
||||
return this.resourceUrlProvider;
|
||||
}
|
||||
return (ResourceUrlProvider) request.getAttribute(
|
||||
ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.servlet.resource;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Unit tests for {@code LinkRewriteTransformer}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class LinkRewriteTransformerTests {
|
||||
|
||||
private ResourceTransformerChain transformerChain;
|
||||
|
||||
private TestTransformer transformer;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<>();
|
||||
resolvers.add(versionResolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
this.transformerChain = new DefaultResourceTransformerChain(new DefaultResourceResolverChain(resolvers), null);
|
||||
|
||||
List<Resource> locations = new ArrayList<>();
|
||||
locations.add(new ClassPathResource("test/", getClass()));
|
||||
|
||||
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
|
||||
handler.setLocations(locations);
|
||||
handler.setResourceResolvers(resolvers);
|
||||
|
||||
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
|
||||
urlProvider.setHandlerMap(Collections.singletonMap("/resources/**", handler));
|
||||
|
||||
this.transformer = new TestTransformer();
|
||||
this.transformer.setResourceUrlProvider(urlProvider);
|
||||
|
||||
this.request = new MockHttpServletRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rewriteAbsolutePath() throws Exception {
|
||||
this.request.setRequestURI("/servlet/context/resources/main.css");
|
||||
this.request.setMethod("GET");
|
||||
this.request.setServletPath("/servlet");
|
||||
this.request.setContextPath("/context");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/resources/main.css");
|
||||
|
||||
String resourcePath = "/servlet/context/resources/bar.css";
|
||||
Resource mainCss = new ClassPathResource("test/main.css", getClass());
|
||||
String actual = this.transformer.resolveUrlPath(resourcePath, this.request, mainCss, this.transformerChain);
|
||||
assertEquals("/servlet/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
|
||||
actual = this.transformer.resolveUrlPath("bar.css", this.request, mainCss, this.transformerChain);
|
||||
assertEquals("bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rewriteRelativePath() throws Exception {
|
||||
this.request.setRequestURI("/servlet/context/resources/main.css");
|
||||
this.request.setMethod("GET");
|
||||
this.request.setServletPath("/servlet");
|
||||
this.request.setContextPath("/context");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/resources/main.css");
|
||||
|
||||
Resource mainCss = new ClassPathResource("test/main.css", getClass());
|
||||
String actual = this.transformer.resolveUrlPath("bar.css", this.request, mainCss, this.transformerChain);
|
||||
assertEquals("bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rewriteRelativePathUpperLevel() throws Exception {
|
||||
this.request.setRequestURI("/servlet/context/resources/images/image.png");
|
||||
this.request.setMethod("GET");
|
||||
this.request.setServletPath("/servlet");
|
||||
this.request.setContextPath("/context");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/resources/images/image.png");
|
||||
|
||||
Resource imagePng = new ClassPathResource("test/images/image.png", getClass());
|
||||
String actual = this.transformer.resolveUrlPath("../bar.css", this.request, imagePng, this.transformerChain);
|
||||
assertEquals("../bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
}
|
||||
|
||||
|
||||
private static class TestTransformer extends ResourceTransformerSupport {
|
||||
|
||||
@Override
|
||||
public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain chain) {
|
||||
throw new IllegalStateException("Should never be called");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue