无法使用 Spring Webflux 和 Thymeleaf 对静态资源进行版本控制

Posted

技术标签:

【中文标题】无法使用 Spring Webflux 和 Thymeleaf 对静态资源进行版本控制【英文标题】:Not able to version static resources with Spring Webflux and Thymeleaf 【发布时间】:2019-05-16 00:01:18 【问题描述】:

我尝试使用 Spring Webflux 在我的应用程序上实现静态内容版本控制,如文档中所述:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web-reactive.html#webflux-config-static-resources

我有以下使用版本资源解析器的资源处理程序:

    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) 

        final VersionResourceResolver versionResolver = new VersionResourceResolver();

        versionResolver.addFixedVersionStrategy("test_version", "/**");

        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .resourceChain(false)
                .addResolver(versionResolver);
    

使用此代码,我希望将所有静态存储到浏览器请求的类路径中的“/static/”中,并在 URL 中添加“test_version”。

但是,在请求的所有资源中,只有少数请求是通过 URL 中的版本完成的。

例如,以下是请求图像的示例:

这是唯一请求的带有 URL 中添加的版本的图像:

我的 JS 或 CSS 资源没有两个版本。我没有看到版本化资源和非版本化资源之间的 HTTP 级别(标头等)有任何差异,来自 ETag 标头。

这是我的项目配置:Spring Boot 2.1.0.RELEASE 和 Spring Webflux、Thymeleaf 3.0.11.RELEASE、Netty 服务器。

为什么我的一些资源由 VersionResourceResolver 处理,而另一些不是?

更新

我在示例应用程序中重现了该问题:https://github.com/adsanche/statics-versioning-problem

此应用程序仅包含从 Spring Boot 2.1.0.RELEASE 开始的 Webflux 和 Thymeleaf。

这是我在 application.yml 中的版本控制配置:

spring:
  resources:
    chain:
      strategy:
        fixed:
          enabled: true
          version: test_version

请注意,通过模板请求的图像没有版本控制:

<!-- Can't version this image -->
<img th:src="@/images/not_versioned.png">

但是,通过 CSS 请求的图像版本控制良好:

.test 
    background: url('/images/versioned.png');

我通过 Thymeleaf 请求的 CSS 文件也没有版本化:

<head>
    <link rel="stylesheet" th:href="@/css/main.css"/>
</head>

这与 Sring Boot 2.1.1.RELEASE 相同。

我是否在我的 Spring Boot/Thymeleaf 配置中遗漏了什么,或者这可能是一个问题?

更新 2

我可能对这种行为有一些解释。

通过 CSS 文件调用的资源似乎是版本化的,因为在 ResourceUrlProvider 中有一个 CssLinkResourceTransformer 调用 getForUriString 方法,该方法使用 VersionResourceResolver 来解析资源路径。

但是,当从模板调用时,SpringWebFluxTemplateEngine 似乎通过SpringWebFluxLinkBuilder 解析资源路径,其processLink 方法似乎只是返回提供的资源路径,没有转换或调用ResourceUrlProvider

目前我发现的唯一解决方法是以这种方式覆盖 SpringWebFluxLinkBuilder 并将其设置为 SpringWebFluxTemplateEngine

@Override
public String processLink(IExpressionContext context, String link) 

    return super.processLink(context, "test_version" + link);

使用该代码,我可以对模板中调用的所有 CSS/JS/图像进行版本控制。但是,我想知道这是否有必要,或者我是否只是缺少一些配置点来自动触发此逻辑。

【问题讨论】:

【参考方案1】:

首先,您应该放弃自定义配置并依赖配置属性,因为您没有做任何 Spring Boot 已经实现的事情:

spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.version=test_version

资源 URL 在服务器端由 Spring Framework 中的资源处理支持重写。

首先,您应该确保它已启用:

    在 Spring Boot 的情况下,您不应该有 @EnableWebMvc@EnableWebFlux,因为它们会禁用 Web 自动配置 资源链接应位于模板文件或由 Spring 重写的文件(例如 CSS 文件)中 资源 URL 应由模板引擎编码,如 &lt;link rel="stylesheet" th:href="@/static/css/spring.css"&gt;

在 Spring WebFlux 的情况下,像 CSS 文件这样的资源在提供服务时会被资源处理程序重写。 Spring MVC 支持重写模板引擎中的资源链接,但 Spring WebFlux 目前不支持。

您可以看到ResourceUrlProvider 合约是异步的,因为它返回Mono 类型。另一方面,SpringWebFluxLinkBuilder 期望阻塞合约。我并不是说 Thymeleaf 有错,因为 Thymeleaf 已经在以反应方式渲染模板。但为了渲染这些链接,我们需要潜在地读取整个资源并计算其哈希值——我们不能在渲染模板的过程中这样做。

您可以在SPR-15012 中了解更多相关信息,它解释了当前的状态。随意对这张票发表评论——也许事情在此期间发生了变化,或者 Spring Framework / Spring Boot 参考文档中可能缺少某些内容。

【讨论】:

Brian,我刚刚用一个示例项目更新了我的帖子,重现了我的问题:github.com/adsanche/statics-versioning-problem 我可以就我的深入分析和示例项目中重现的问题提供反馈吗?我想在 Thymeleaf 或 Spring Boot 项目上打开一个关于它的问题。 我已经编辑了我的答案;不幸的是,这是目前的情况。抱歉,我的第一个答案具有误导性,因为我完全忘记了这个限制。 我明白,我将坚持我的解决方法,即覆盖 Thymeleaf SpringWebFluxLinkBuilder 并手动重写我的 Spring 版本控制配置中授予的路径的资源 URL,它可以完成工作为了我。但是,是的,让它像在 Spring MVC 中一样透明地工作将是一件好事。 @BrianClozel 我在您提到的票证中找不到任何新信息。有这方面的消息吗?我尝试使用内容策略对 css 文件进行哈希处理,但正如您在此处描述的那样,这不起作用。也许随着时间的推移有所改进?

以上是关于无法使用 Spring Webflux 和 Thymeleaf 对静态资源进行版本控制的主要内容,如果未能解决你的问题,请参考以下文章

Spring webflux 和 Angular

使用Webflux和Spring Cloud时如何用netty替换tomcat?

Spring Boot v2.0.0.M2 webflux 和执行器

spring webflux(netty)处理程序无法解析包含大于 750 字节的 json 的 ServerRequest

spring 5 webflux 功能端点请求中不存在访问控制源头

Spring Boot Webflux 无法返回 application/xml