Spring 使用 ContentVersionStrategy 的启动缓存禁止使用 gzip 进行资源压缩
Spring Boot caching with ContentVersionStrategy prohibits resource compression with gzip
我们有一个 Spring 使用 Thymeleaf 启动 Web 应用程序。
在 HTML 模板中,我们引用了一些静态资源,例如/src/main/resources/static/js/main.js
通过 <script defer th:src="@{/js/main.js}"></script>
.
为了允许浏览器为多次访问我们的网站缓存静态资源,我们启用了内容版本控制:
spring.resources:
chain:
strategy.content:
enabled: true
paths: /**
cache.cachecontrol.max-age: 365d
一切正常,我们得到了资源,其 MD5 散列附加到文件名(例如 /main-d9f17fd70ee583fef4acf26dd331b8ab.js
)。
为了进一步减少流量,我们现在想要使用 gzip 启用资源压缩:
server:
compression:
enabled: true
mime-types: application/javascript,and-some-others
min-response-size: 1024
当使用 header Accept-Encoding='gzip'
请求(版本化)资源时,我们没有收到 Content-Encoding='gzip'
的响应。因此,资源压缩似乎无法与内容版本控制结合使用。
如果我们禁用内容版本控制,资源压缩工作正常:Content-Encoding='gzip'
header 设置为(现在 non-versioned)资源。
所以我们深入 Spring 的内部并发现以下内容:
org.springframework.web.servlet.resource.VersionResourceResolver#getResponseHeaders
始终设置(强)ETag header:
public HttpHeaders getResponseHeaders() {
HttpHeaders headers = (this.original instanceof HttpResource ?
((HttpResource) this.original).getResponseHeaders() : new HttpHeaders());
headers.setETag("\"" + this.version + "\"");
return headers;
}
org.apache.coyote.CompressionConfig#useCompression
禁用压缩,如果有强 ETag:
public boolean useCompression(Request request, Response response) {
...
if (noCompressionStrongETag) {
String eTag = responseHeaders.getHeader("ETag");
if (eTag != null && !eTag.trim().startsWith("W/")) {
// Has an ETag that doesn't start with "W/..." so it must be a
// strong ETag
return false;
}
}
...
}
您可以将 noCompressionStrongETag
设置为 false,但这已被弃用,将在 Tomcat 10...
中删除
为了演示这个问题,我创建了一个 example project in Github,其中包含三个通过的测试和一个失败的测试,显示了我们的期望未达到的地方...
你知道如何解决这个矛盾吗?我们是不是做错了什么?
该问题现已在 Spring's Github repository 中进行跟踪。目前的想法是在 Spring 的 VersionResourceResolver
.
中从强 ETag 切换到弱 ETag
更新:问题已通过 Spring Boot 2.4 解决,Spring 5.3 附带。包含修复程序。
贡献者在问题中强调当前行为与 Tomcat 中解决的 here 中的修复相关联。在那里,得出的结论是响应压缩违反了强 ETag 的含义,因此是一个错误。因此,Tomcat 仅在不存在或仅存在弱 ETag header 时应用压缩(这与我在 SO 问题中描述的观察结果一致)。
我们有一个 Spring 使用 Thymeleaf 启动 Web 应用程序。
在 HTML 模板中,我们引用了一些静态资源,例如/src/main/resources/static/js/main.js
通过 <script defer th:src="@{/js/main.js}"></script>
.
为了允许浏览器为多次访问我们的网站缓存静态资源,我们启用了内容版本控制:
spring.resources:
chain:
strategy.content:
enabled: true
paths: /**
cache.cachecontrol.max-age: 365d
一切正常,我们得到了资源,其 MD5 散列附加到文件名(例如 /main-d9f17fd70ee583fef4acf26dd331b8ab.js
)。
为了进一步减少流量,我们现在想要使用 gzip 启用资源压缩:
server:
compression:
enabled: true
mime-types: application/javascript,and-some-others
min-response-size: 1024
当使用 header Accept-Encoding='gzip'
请求(版本化)资源时,我们没有收到 Content-Encoding='gzip'
的响应。因此,资源压缩似乎无法与内容版本控制结合使用。
如果我们禁用内容版本控制,资源压缩工作正常:Content-Encoding='gzip'
header 设置为(现在 non-versioned)资源。
所以我们深入 Spring 的内部并发现以下内容:
org.springframework.web.servlet.resource.VersionResourceResolver#getResponseHeaders
始终设置(强)ETag header:
public HttpHeaders getResponseHeaders() {
HttpHeaders headers = (this.original instanceof HttpResource ?
((HttpResource) this.original).getResponseHeaders() : new HttpHeaders());
headers.setETag("\"" + this.version + "\"");
return headers;
}
org.apache.coyote.CompressionConfig#useCompression
禁用压缩,如果有强 ETag:
public boolean useCompression(Request request, Response response) {
...
if (noCompressionStrongETag) {
String eTag = responseHeaders.getHeader("ETag");
if (eTag != null && !eTag.trim().startsWith("W/")) {
// Has an ETag that doesn't start with "W/..." so it must be a
// strong ETag
return false;
}
}
...
}
您可以将 noCompressionStrongETag
设置为 false,但这已被弃用,将在 Tomcat 10...
为了演示这个问题,我创建了一个 example project in Github,其中包含三个通过的测试和一个失败的测试,显示了我们的期望未达到的地方...
你知道如何解决这个矛盾吗?我们是不是做错了什么?
该问题现已在 Spring's Github repository 中进行跟踪。目前的想法是在 Spring 的 VersionResourceResolver
.
更新:问题已通过 Spring Boot 2.4 解决,Spring 5.3 附带。包含修复程序。
贡献者在问题中强调当前行为与 Tomcat 中解决的 here 中的修复相关联。在那里,得出的结论是响应压缩违反了强 ETag 的含义,因此是一个错误。因此,Tomcat 仅在不存在或仅存在弱 ETag header 时应用压缩(这与我在 SO 问题中描述的观察结果一致)。