Browse Source

Prevent resource transformation of gzipped CSS files

When resolved through the `GzipResourceResolver`, CSS files can be
resolved as their pre-gzipped variant, if a ".gz" file is present in the
configured resource locations.

Such resources are gzipped and thus should not be transformed by
`CssLinkResourceTransformer`s, since rewriting those would need to
uncompress/transform/recompress. This would lead to poorer performances
than resolving plain resources and delegating compression to the
container.

This commit checks for `GzippedResource` instances in
`CssLinkResourceTransformer` and avoids processing them.

Issue: SPR-14773
pull/1238/head
Brian Clozel 10 years ago
parent
commit
cb44f2746e
  1. 3
      spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/CssLinkResourceTransformer.java
  2. 2
      spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/GzipResourceResolver.java
  3. 69
      spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/CssLinkResourceTransformerTests.java
  4. 3
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java
  5. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/GzipResourceResolver.java
  6. 28
      spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java

3
spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/CssLinkResourceTransformer.java

@ -73,7 +73,8 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
return transformerChain.transform(exchange, resource) return transformerChain.transform(exchange, resource)
.then(newResource -> { .then(newResource -> {
String filename = newResource.getFilename(); String filename = newResource.getFilename();
if (!"css".equals(StringUtils.getFilenameExtension(filename))) { if (!"css".equals(StringUtils.getFilenameExtension(filename)) ||
resource instanceof GzipResourceResolver.GzippedResource) {
return Mono.just(newResource); return Mono.just(newResource);
} }

2
spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/GzipResourceResolver.java

@ -76,7 +76,7 @@ public class GzipResourceResolver extends AbstractResourceResolver {
} }
private static final class GzippedResource extends AbstractResource implements HttpResource { static final class GzippedResource extends AbstractResource implements HttpResource {
private final Resource original; private final Resource original;

69
spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/CssLinkResourceTransformerTests.java

@ -16,6 +16,11 @@
package org.springframework.web.reactive.resource; package org.springframework.web.reactive.resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -23,6 +28,7 @@ import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -80,9 +86,6 @@ public class CssLinkResourceTransformerTests {
public void transform() throws Exception { public void transform() throws Exception {
initExchange(HttpMethod.GET, "/static/main.css"); initExchange(HttpMethod.GET, "/static/main.css");
Resource css = new ClassPathResource("test/main.css", getClass()); Resource css = new ClassPathResource("test/main.css", getClass());
TransformedResource actual =
(TransformedResource) this.transformerChain.transform(this.exchange, css)
.blockMillis(5000);
String expected = "\n" + String expected = "\n" +
"@import url(\"/static/bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" + "@import url(\"/static/bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" +
@ -92,17 +95,24 @@ public class CssLinkResourceTransformerTests {
"@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" + "@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" +
"body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n"; "body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n";
String result = new String(actual.getByteArray(), "UTF-8"); StepVerifier.create(this.transformerChain.transform(this.exchange, css).cast(TransformedResource.class))
result = StringUtils.deleteAny(result, "\r"); .consumeNextWith(resource -> {
assertEquals(expected, result); String result = new String(resource.getByteArray(), StandardCharsets.UTF_8);
result = StringUtils.deleteAny(result, "\r");
assertEquals(expected, result);
})
.expectComplete().verify();
} }
@Test @Test
public void transformNoLinks() throws Exception { public void transformNoLinks() throws Exception {
initExchange(HttpMethod.GET, "/static/foo.css"); initExchange(HttpMethod.GET, "/static/foo.css");
Resource expected = new ClassPathResource("test/foo.css", getClass()); Resource expected = new ClassPathResource("test/foo.css", getClass());
Resource actual = this.transformerChain.transform(this.exchange, expected).blockMillis(5000); StepVerifier.create(this.transformerChain.transform(this.exchange, expected))
assertSame(expected, actual); .consumeNextWith(resource -> {
assertSame(expected, resource);
})
.expectComplete().verify();
} }
@Test @Test
@ -113,15 +123,15 @@ public class CssLinkResourceTransformerTests {
Collections.singletonList(new CssLinkResourceTransformer())); Collections.singletonList(new CssLinkResourceTransformer()));
Resource externalCss = new ClassPathResource("test/external.css", getClass()); Resource externalCss = new ClassPathResource("test/external.css", getClass());
Resource resource = transformerChain.transform(this.exchange, externalCss).blockMillis(5000); StepVerifier.create(transformerChain.transform(this.exchange, externalCss).cast(TransformedResource.class))
TransformedResource transformedResource = (TransformedResource) resource; .consumeNextWith(resource -> {
String expected = "@import url(\"http://example.org/fonts/css\");\n" +
String expected = "@import url(\"http://example.org/fonts/css\");\n" + "body { background: url(\"file:///home/spring/image.png\") }\n" +
"body { background: url(\"file:///home/spring/image.png\") }\n" + "figure { background: url(\"//example.org/style.css\")}";
"figure { background: url(\"//example.org/style.css\")}"; String result = new String(resource.getByteArray(), StandardCharsets.UTF_8);
String result = new String(transformedResource.getByteArray(), "UTF-8"); result = StringUtils.deleteAny(result, "\r");
result = StringUtils.deleteAny(result, "\r"); assertEquals(expected, result);
assertEquals(expected, result); }).expectComplete().verify();
Mockito.verify(resolverChain, Mockito.never()) Mockito.verify(resolverChain, Mockito.never())
.resolveUrlPath("http://example.org/fonts/css", Collections.singletonList(externalCss)); .resolveUrlPath("http://example.org/fonts/css", Collections.singletonList(externalCss));
@ -135,8 +145,29 @@ public class CssLinkResourceTransformerTests {
public void transformWithNonCssResource() throws Exception { public void transformWithNonCssResource() throws Exception {
initExchange(HttpMethod.GET, "/static/images/image.png"); initExchange(HttpMethod.GET, "/static/images/image.png");
Resource expected = new ClassPathResource("test/images/image.png", getClass()); Resource expected = new ClassPathResource("test/images/image.png", getClass());
Resource actual = this.transformerChain.transform(this.exchange, expected).blockMillis(5000); StepVerifier.create(this.transformerChain.transform(this.exchange, expected))
assertSame(expected, actual); .expectNext(expected)
.expectComplete().verify();
}
@Test
public void transformWithGzippedResource() throws Exception {
initExchange(HttpMethod.GET, "/static/main.css");
Resource original = new ClassPathResource("test/main.css", getClass());
createTempCopy("main.css", "main.css.gz");
GzipResourceResolver.GzippedResource expected = new GzipResourceResolver.GzippedResource(original);
StepVerifier.create(this.transformerChain.transform(this.exchange, expected))
.expectNext(expected)
.expectComplete().verify();
}
private void createTempCopy(String filePath, String copyFilePath) throws IOException {
Resource location = new ClassPathResource("test/", CssLinkResourceTransformerTests.class);
Path original = Paths.get(location.getFile().getAbsolutePath(), filePath);
Path copy = Paths.get(location.getFile().getAbsolutePath(), copyFilePath);
Files.deleteIfExists(copy);
Files.copy(original, copy);
copy.toFile().deleteOnExit();
} }
private void initExchange(HttpMethod method, String url) { private void initExchange(HttpMethod method, String url) {

3
spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java

@ -70,7 +70,8 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
resource = transformerChain.transform(request, resource); resource = transformerChain.transform(request, resource);
String filename = resource.getFilename(); String filename = resource.getFilename();
if (!"css".equals(StringUtils.getFilenameExtension(filename))) { if (!"css".equals(StringUtils.getFilenameExtension(filename)) ||
resource instanceof GzipResourceResolver.GzippedResource) {
return resource; return resource;
} }

2
spring-webmvc/src/main/java/org/springframework/web/servlet/resource/GzipResourceResolver.java

@ -78,7 +78,7 @@ public class GzipResourceResolver extends AbstractResourceResolver {
} }
private static final class GzippedResource extends AbstractResource implements HttpResource { static final class GzippedResource extends AbstractResource implements HttpResource {
private final Resource original; private final Resource original;

28
spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java

@ -16,6 +16,11 @@
package org.springframework.web.servlet.resource; package org.springframework.web.servlet.resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -87,7 +92,7 @@ public class CssLinkResourceTransformerTests {
"@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" + "@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" +
"body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n"; "body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n";
String result = new String(actual.getByteArray(), "UTF-8"); String result = new String(actual.getByteArray(), StandardCharsets.UTF_8);
result = StringUtils.deleteAny(result, "\r"); result = StringUtils.deleteAny(result, "\r");
assertEquals(expected, result); assertEquals(expected, result);
} }
@ -114,7 +119,7 @@ public class CssLinkResourceTransformerTests {
String expected = "@import url(\"http://example.org/fonts/css\");\n" + String expected = "@import url(\"http://example.org/fonts/css\");\n" +
"body { background: url(\"file:///home/spring/image.png\") }\n" + "body { background: url(\"file:///home/spring/image.png\") }\n" +
"figure { background: url(\"//example.org/style.css\")}"; "figure { background: url(\"//example.org/style.css\")}";
String result = new String(transformedResource.getByteArray(), "UTF-8"); String result = new String(transformedResource.getByteArray(), StandardCharsets.UTF_8);
result = StringUtils.deleteAny(result, "\r"); result = StringUtils.deleteAny(result, "\r");
assertEquals(expected, result); assertEquals(expected, result);
@ -134,4 +139,23 @@ public class CssLinkResourceTransformerTests {
assertSame(expected, actual); assertSame(expected, actual);
} }
@Test
public void transformWithGzippedResource() throws Exception {
this.request = new MockHttpServletRequest("GET", "/static/main.css");
Resource original = new ClassPathResource("test/main.css", getClass());
createTempCopy("main.css", "main.css.gz");
GzipResourceResolver.GzippedResource expected = new GzipResourceResolver.GzippedResource(original);
Resource actual = this.transformerChain.transform(this.request, expected);
assertSame(expected, actual);
}
private void createTempCopy(String filePath, String copyFilePath) throws IOException {
Resource location = new ClassPathResource("test/", CssLinkResourceTransformerTests.class);
Path original = Paths.get(location.getFile().getAbsolutePath(), filePath);
Path copy = Paths.get(location.getFile().getAbsolutePath(), copyFilePath);
Files.deleteIfExists(copy);
Files.copy(original, copy);
copy.toFile().deleteOnExit();
}
} }

Loading…
Cancel
Save