完整 URL 在 assets erb 文件中——>多站点问题

新的 Workbox 服务工作者实现使用了 UrlHelper.absolute。由于这是一个编译后的资源,它会存储完整的 URL,其中包含多站点环境中主主机的完整主机名。

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/service-worker.js.erb#L3-L6

我认为应该改用 UrlHelper.local_cdn_url。这样也能避免在更改主机名后需要重新编译资源。

1 个赞

我认为普遍共识是:Service Workers 绝不应通过 CDN 提供:

哦,你的意思是 importScripts 文件吗?你们是否有一个多站点集群,其中每个站点都有不同的 CDN URL?

1 个赞

是的,在那个 ERB 文件的第 3 行和第 6 行,使用了 importScriptsmodulePathPrefix

但你还没有完全理解我的意思。问题出现在多站点集群中没有 CDN 的情况下。

UrlHelper.absoluteUrlHelper.local_cdn_url 都能处理有 CDN 和无 CDN 的情况。

absolute

无 CDN:https://primarysite.ofmultisitecluster.com/javascripts/workbox/workbox-sw.js 不好——对所有非主站点使用远程源,暴露了主集群的主机名
有 CDN://cdnurl/javascripts/workbox/workbox-sw.js

local_cdn_url

无 CDN:/javascripts/workbox/workbox-sw.js 好——相对 URL
有 CDN://cdnurl/javascripts/workbox/workbox-sw.js

因此,后者才是我们想要的。

问题在于,第 3 行和第 6 行并非关于服务工作线程文件本身。

服务工作线程来自基础域名(因为它不能通过 CDN 提供),但该服务工作线程会在运行时懒加载脚本(在此例中是 Workbox 库文件),而这些文件可以通过 CDN 提供。

因此,问题出在你的多站点集群未配置 CDN,这才暴露了该漏洞;而当设置了 DISCOURSE_CDN 时,该漏洞会被掩盖。我只是想了解一下为什么这不会影响我们。

1 个赞

你说得对,我已经编辑了我的第二篇帖子来修正我的错误。
问题不在于服务工作者文件本身,而是就在这个服务工作者文件里。

是的,正是在这种情况下 bug 才会暴露出来。

3 个赞

只是想确认一下——我的诊断是否正确?这是否是一个即将修复的 bug?有什么需要我们提供以协助解决的吗?

3 个赞

是的,这看起来是一个需要修复的 bug。我本周就会处理!

3 个赞

嗯,我刚刚在 Meta 控制台上试了一下:

## 当前实现
[1] pry(main)> UrlHelper.absolute("/javascripts/workbox/workbox-sw.js")
=> "https://d3bpeqsaub0i6y.cloudfront.net/javascripts/workbox/workbox-sw.js"

### 拟议的更改
[2] pry(main)> UrlHelper.local_cdn_url("/javascripts/workbox/workbox-sw.js")
=> "/javascripts/workbox/workbox-sw.js"

在我看来,这两个函数似乎不能互相替换。

3 个赞

你说得对,local_cdn_url 会将本地 URL 替换为 CDN URL。而且这甚至不是本地 URL,而是一个相对路径。

所以我认为用这些来代替那些 UrlHelper 调用就足够了?

importScripts("<%= (Discourse.asset_host || '') + "/javascripts/workbox/workbox-sw.js" %>");

以及

modulePathPrefix: (Discourse.asset_host || '') + "/javascripts/workbox",

1 个赞

阅读 UrlHelper.absolute 的当前实现:

看起来当 CDN 为 nil 时(这正是您的情况),它会通过拼接 Discourse.base_url_no_prefix 和参数来构建 URL。

所以问题在于,Discourse.base_url_no_prefix 在多站点环境中是否总是返回第一个主机?

查看代码 :eyes:

第 288 行变量名 current_hostname 强烈暗示它已考虑到多站点场景 :thinking:

再看这里:

看起来确实如此。目前看来是个死胡同…

再看看其他地方,这个路由之所以有特殊处理,是因为浏览器非常喜欢频繁访问它,而我们不能将其放到 CDN 上把问题甩给别人。在这样做的时候,我们曾遇到过涉及多站点泄露的 bug,该问题已在一年前由 @sam 修复:

是否存在这样一种可能性:您部署该多站点集群的方式以类似 2018 年初我们遇到的那种“泄露式”缓存方式缓存了这个路由?

2 个赞

不,问题在于它在预编译资源时这样做,这在多站点环境中会引发问题。
因此,解决方案是永远不要在资源中包含主机名(除非配置了资源 CDN,因为该 CDN 在多站点主机之间始终是共享的)。

1 个赞

@falco 你看到我在上面两帖中提出的解决方案了吗?

是的,但在我的测试中,如果没有 CDN,它无法覆盖子文件夹😭

我认为我们需要使用:

"#{Discourse.asset_host}#{Discourse.base_prefix}/javascripts/workbox"
1 个赞

嗯,关于子文件夹这一点说得很好。

但是……Discourse.asset_host 可能是 nil,而我从未听说过 Discourse.base_prefix

不如这样:

importScripts("<%= (Discourse.asset_host || GlobalSetting.relative_url_root) + "/javascripts/workbox/workbox-sw.js" %>");

modulePathPrefix: "<%= (Discourse.asset_host || GlobalSetting.relative_url_root) + "/javascripts/workbox" %>",

这正是我们在此情况下想要的:

irb(main):001:0> puts "a#{nil}bc"
abc

哦,我指的是 Discourse.base_path

已提交修复,请查看。

3 个赞

这对我们的情况看起来不错……谢谢!

不过……为了确保万无一失……我不太确定你们是如何在 CDN 上处理资产并结合子文件夹的。但如果你们使用了 Discourse.asset_host,是否仍然会在资产主机上的所有路径前加上子文件夹路径?因为目前的代码就是这样做的。
如果确实如此,那么你们完全可以忽略这一段:slight_smile

3 个赞

这个子文件夹 + CDN 的组合确实给我们带来了不少麻烦。@featheredtoast 做了一些工作来优化这一流程,我们花费了大量时间确保我们的资源服务代码能够在这些奇怪的存储桶、子文件夹等组合中正常运行。

认为我们现在已经安全了 :smile:,但如果需要,我们可以重新打开这个问题。

感谢你的错误报告!

8 个赞

此主题已在 7 天后自动关闭。不再允许新回复。