diff --git a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc index 9c255395a730..800e5f735010 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc @@ -620,7 +620,7 @@ resource URLs, since there are no view technologies that can make use of a non-b of resolvers and transformers. When serving only local resources, the workaround is to use `ResourceUrlProvider` directly (for example, through a custom element) and block. -Note that, when using both `EncodedResourceResolver` (for example, Gzip, Brotli encoded) and +Note that, when using both `EncodedResourceResolver` (for example, Gzip, Brotli, Zstd encoded) and `VersionedResourceResolver`, they must be registered in that order, to ensure content-based versions are always computed reliably based on the unencoded file. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc index f99633545081..faa32b0b197d 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc @@ -40,8 +40,8 @@ bean so that it can be injected into others. You can also make the rewrite trans `ResourceUrlEncodingFilter` for Thymeleaf, JSPs, FreeMarker, and others with URL tags that rely on `HttpServletResponse#encodeURL`. -Note that, when using both `EncodedResourceResolver` (for example, for serving gzipped or -brotli-encoded resources) and `VersionResourceResolver`, you must register them in this order. +Note that, when using both `EncodedResourceResolver` (for example, for serving gzipped, brotli, +or zstd encoded resources) and `VersionResourceResolver`, you must register them in this order. That ensures content-based versions are always computed reliably, based on the unencoded file. For https://www.webjars.org/documentation[WebJars], versioned URLs like diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java index b7d205bc55c5..fb75e64e1cc9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java @@ -83,7 +83,7 @@ public Cache getCache() { * {@literal "Accept-Encoding"} header for which to cache resource variations. *

The codings configured here are generally expected to match those * configured on {@link EncodedResourceResolver#setContentCodings(List)}. - *

By default this property is set to {@literal ["br", "gzip"]} based on + *

By default this property is set to {@literal ["zstd", "br", "gzip"]} based on * the value of {@link EncodedResourceResolver#DEFAULT_CODINGS}. * @param codings one or more supported content codings * @since 5.1 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java index 000af7a8e8e9..c10110082068 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java @@ -44,8 +44,8 @@ /** * Resolver that delegates to the chain, and if a resource is found, it then - * attempts to find an encoded (for example, gzip, brotli) variant that is acceptable - * based on the "Accept-Encoding" request header. + * attempts to find an encoded (for example, gzip, brotli, zstd) variant that is + * acceptable based on the "Accept-Encoding" request header. * *

The list of supported {@link #setContentCodings(List) contentCodings} can * be configured, in order of preference, and each coding must be associated @@ -56,6 +56,7 @@ * ensure the version calculation is not impacted by the encoding. * * @author Rossen Stoyanchev + * @author Toshiaki Maki * @since 5.1 */ public class EncodedResourceResolver extends AbstractResourceResolver { @@ -63,7 +64,7 @@ public class EncodedResourceResolver extends AbstractResourceResolver { /** * The default content codings. */ - public static final List DEFAULT_CODINGS = Arrays.asList("br", "gzip"); + public static final List DEFAULT_CODINGS = Arrays.asList("zstd", "br", "gzip"); private final List contentCodings = new ArrayList<>(DEFAULT_CODINGS); @@ -74,6 +75,7 @@ public class EncodedResourceResolver extends AbstractResourceResolver { public EncodedResourceResolver() { this.extensions.put("gzip", ".gz"); this.extensions.put("br", ".br"); + this.extensions.put("zstd", ".zst"); } @@ -87,7 +89,7 @@ public EncodedResourceResolver() { * customizations to the list of codings here should be matched by * customizations to the same list in {@link CachingResourceResolver} to * ensure encoded variants of a resource are cached under separate keys. - *

By default this property is set to {@literal ["br", "gzip"]}. + *

By default this property is set to {@literal ["zstd", "br", "gzip"]}. * @param codings one or more supported content codings */ public void setContentCodings(List codings) { @@ -106,8 +108,8 @@ public List getContentCodings() { /** * Configure mappings from content codings to file extensions. A dot "." * will be prepended in front of the extension value if not present. - *

By default this is configured with {@literal ["br" -> ".br"]} and - * {@literal ["gzip" -> ".gz"]}. + *

By default this is configured with {@literal ["zstd" -> ".zst"]}, + * {@literal ["br" -> ".br"]} and {@literal ["gzip" -> ".gz"]}. * @param extensions the extensions to use. * @see #registerExtension(String, String) */ diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/EncodedResourceResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/EncodedResourceResolverTests.java index 6b4568ca2f01..802e75f2fced 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/EncodedResourceResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/EncodedResourceResolverTests.java @@ -40,6 +40,7 @@ * Tests for {@link EncodedResourceResolver}. * * @author Rossen Stoyanchev + * @author Toshiaki Maki */ @ExtendWith(GzipSupport.class) class EncodedResourceResolverTests { @@ -91,6 +92,24 @@ void resolveGzipped(GzippedFiles gzippedFiles) { assertThat(headers.getFirst(HttpHeaders.VARY)).isEqualTo("Accept-Encoding"); } + @Test + void resolveZstd() { + + MockServerWebExchange exchange = MockServerWebExchange.from( + MockServerHttpRequest.get("").header("Accept-Encoding", "zstd")); + + String file = "js/foo.js"; + Resource actual = this.resolver.resolveResource(exchange, file, this.locations).block(TIMEOUT); + + assertThat(actual.getDescription()).isEqualTo(getResource(file + ".zst").getDescription()); + assertThat(actual.getFilename()).isEqualTo(getResource(file).getFilename()); + + assertThat(actual).isInstanceOf(HttpResource.class); + HttpHeaders headers = ((HttpResource) actual).getResponseHeaders(); + assertThat(headers.getFirst(HttpHeaders.CONTENT_ENCODING)).isEqualTo("zstd"); + assertThat(headers.getFirst(HttpHeaders.VARY)).isEqualTo("Accept-Encoding"); + } + @Test void resolveGzippedWithVersion(GzippedFiles gzippedFiles) { diff --git a/spring-webflux/src/test/resources/org/springframework/web/reactive/resource/test/js/foo.js.zst b/spring-webflux/src/test/resources/org/springframework/web/reactive/resource/test/js/foo.js.zst new file mode 100644 index 000000000000..3edbf1399f7b Binary files /dev/null and b/spring-webflux/src/test/resources/org/springframework/web/reactive/resource/test/js/foo.js.zst differ diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java index f8575376d763..92825837d3a9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java @@ -83,7 +83,7 @@ public Cache getCache() { * {@literal "Accept-Encoding"} header for which to cache resource variations. *

The codings configured here are generally expected to match those * configured on {@link EncodedResourceResolver#setContentCodings(List)}. - *

By default this property is set to {@literal ["br", "gzip"]} based on + *

By default this property is set to {@literal ["zstd", "br", "gzip"]} based on * the value of {@link EncodedResourceResolver#DEFAULT_CODINGS}. * @param codings one or more supported content codings * @since 5.1 diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java index b42abc226c1b..e75255ee85fe 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java @@ -43,8 +43,8 @@ /** * Resolver that delegates to the chain, and if a resource is found, it then - * attempts to find an encoded (for example, gzip, brotli) variant that is acceptable - * based on the "Accept-Encoding" request header. + * attempts to find an encoded (for example, gzip, brotli, zstd) variant that is + * acceptable based on the "Accept-Encoding" request header. * *

The list of supported {@link #setContentCodings(List) contentCodings} can * be configured, in order of preference, and each coding must be associated @@ -55,6 +55,7 @@ * ensure the version calculation is not impacted by the encoding. * * @author Rossen Stoyanchev + * @author Toshiaki Maki * @since 5.1 */ public class EncodedResourceResolver extends AbstractResourceResolver { @@ -62,7 +63,7 @@ public class EncodedResourceResolver extends AbstractResourceResolver { /** * The default content codings. */ - public static final List DEFAULT_CODINGS = Arrays.asList("br", "gzip"); + public static final List DEFAULT_CODINGS = Arrays.asList("zstd", "br", "gzip"); private final List contentCodings = new ArrayList<>(DEFAULT_CODINGS); @@ -73,6 +74,7 @@ public class EncodedResourceResolver extends AbstractResourceResolver { public EncodedResourceResolver() { this.extensions.put("gzip", ".gz"); this.extensions.put("br", ".br"); + this.extensions.put("zstd", ".zst"); } @@ -86,7 +88,7 @@ public EncodedResourceResolver() { * customizations to the list of codings here should be matched by * customizations to the same list in {@link CachingResourceResolver} to * ensure encoded variants of a resource are cached under separate keys. - *

By default this property is set to {@literal ["br", "gzip"]}. + *

By default this property is set to {@literal ["zstd", "br", "gzip"]}. * @param codings one or more supported content codings */ public void setContentCodings(List codings) { @@ -105,8 +107,8 @@ public List getContentCodings() { /** * Configure mappings from content codings to file extensions. A dot "." * will be prepended in front of the extension value if not present. - *

By default this is configured with {@literal ["br" -> ".br"]} and - * {@literal ["gzip" -> ".gz"]}. + *

By default this is configured with {@literal ["zstd" -> ".zst"]}, + * {@literal ["br" -> ".br"]} and {@literal ["gzip" -> ".gz"]}. * @param extensions the extensions to use. * @see #registerExtension(String, String) */ diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/EncodedResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/EncodedResourceResolverTests.java index 4dabb47993a6..9b2ba56d341b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/EncodedResourceResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/EncodedResourceResolverTests.java @@ -39,6 +39,7 @@ * * @author Jeremy Grelle * @author Rossen Stoyanchev + * @author Toshiaki Maki */ @ExtendWith(GzipSupport.class) class EncodedResourceResolverTests { @@ -84,6 +85,22 @@ void resolveGzipped(GzippedFiles gzippedFiles) { assertThat(headers.getFirst(HttpHeaders.VARY)).isEqualTo("Accept-Encoding"); } + @Test + void resolveZstd() { + String file = "js/foo.js"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Accept-Encoding", "zstd"); + Resource actual = this.resolver.resolveResource(request, file, this.locations); + + assertThat(actual.getDescription()).isEqualTo(getResource(file + ".zst").getDescription()); + assertThat(actual.getFilename()).isEqualTo(getResource(file).getFilename()); + + assertThat(actual).isInstanceOf(HttpResource.class); + HttpHeaders headers = ((HttpResource) actual).getResponseHeaders(); + assertThat(headers.getFirst(HttpHeaders.CONTENT_ENCODING)).isEqualTo("zstd"); + assertThat(headers.getFirst(HttpHeaders.VARY)).isEqualTo("Accept-Encoding"); + } + @Test void resolveGzippedWithVersion(GzippedFiles gzippedFiles) { gzippedFiles.create("foo.css"); diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/resource/test/js/foo.js.zst b/spring-webmvc/src/test/resources/org/springframework/web/servlet/resource/test/js/foo.js.zst new file mode 100644 index 000000000000..3edbf1399f7b Binary files /dev/null and b/spring-webmvc/src/test/resources/org/springframework/web/servlet/resource/test/js/foo.js.zst differ