无法使嵌入正常工作

你好,我正在尝试按照嵌入指南将 Discourse 评论嵌入到我的网站中,但遇到了瓶颈 :frowning:

症状

我在 Firefox 和 Chrome 中都进行了尝试。在这两种情况下,页面都会加载显示“正在加载讨论…"的 Discourse iframe,但随后卡住,开发者控制台中出现重复的 JavaScript 错误。

在 Firefox 中,我收到了关于 X-Frame-Options 的错误:

加载“https://discourse.29th.local/embed/comments?embed_url=https%3A%2F%2Fpersonnel.29th.local%2F%23enlistments%2F11927”时发现无效的 X-Frame-Options 头:“ALLOWALL”不是有效的指令。

随后在 embed-application.js:7 中出现 DOMException 错误:

未捕获的 DOMException:指定了无效或非法的字符串

这两个错误大约每 30 秒重复一次。网络标签页中没有失败的请求。

在 Chrome 中,我没有收到 X-Frame-Options 错误。几秒钟后,我收到了关于目标源与接收窗口源不匹配的错误:

在 'DOMWindow' 上执行 'postMessage' 失败:提供的目标源 ('https://discourse.29th.local') 与接收窗口的源 ('https://personnel.29th.local') 不匹配。

我在 meta 上看到了很多关于此错误的主题,并尝试了所有故障排除步骤,但均无济于事。

我的环境设置

我遵循了Discourse for Mac 设置指南,但有一个小例外:我没有在笔记本电脑上全局安装 postgres、redis 和 mailcatcher,而是让它们在 Docker 容器中运行,并公开了端口。Discourse 并不知道它们是在 Docker 容器中运行而不是在裸机上运行。Rails/Discourse 是全局安装的,并没有在 Docker 容器中运行。

完全独立地,我的自定义 Web 应用程序运行在一个 Docker Compose 栈中。该栈的一部分包括一个 nginx 服务器,它将 personnel.29th.local 路由到适当的上游容器,将 discourse.29th.local 路由到 host.docker.internal:3000(这是 Docker 容器用来访问主机 localhost 的魔法主机名)。

(正如我在下面提到的,我已经从方程中移除了 nginx 层,但得到了相同的错误)

这里的一个可能陷阱是,我的 Web 应用是一个 JavaScript 单页应用(SPA)。嵌入 Discourse 评论的页面是 https://personnel.29th.local/#enlistments/1234,并且没有服务器端渲染。如果这是一个问题,我期望会出现关于爬虫的错误,那时我就接受 Discourse 仅仅链接到我的应用而不是爬取它。但它显示的错误似乎与爬取失败无关。

故障排除

我已在 管理 > 自定义 > 嵌入 中将可嵌入主机设置为 personnel.29th.local。起初,示例嵌入代码显示 discourseUrlhttp://localhost:3000/,所以我启动了 rails console 并运行:

SiteSetting.force_hostname = "discourse.29th.local"
SiteSetting.port = 443

并在管理仪表板中开启了“强制 HTTPS"。这修复了示例嵌入代码中的 URL。

我还在设置的 cors origins 部分添加了 https://personnel.29th.local 作为 CORS 域。

我现在使用以下命令启动 Discourse:

DISCOURSE_DEV_HOSTS=discourse.29th.local,host.docker.internal DISCOURSE_ENABLE_CORS=true bundle exec rails server

我还尝试在设置仪表板中禁用内容安全策略(CSP)。

我查看了 https://discourse.29th.local/logs/,但没有看到任何错误,也没有关于 Sidekiq 的信息。

关于 Sidekiq,我在管理仪表板上确实看到一条关于更新的消息:

尚未执行更新检查。请确保 Sidekiq 正在运行。

因此,我在 rails console 中运行了 Sidekiq.redis { |r| puts r.flushall } 并得到了 OK,重启了 rails 服务器,但消息和整体问题都没有改变。我在 redis 缓存中四处查看,没有发现与此页面相关的内容。

我还试图通过移除 nginx 层来简化问题:将 SiteSetting.force_hostnameSiteSetting.port 恢复为 nil,关闭强制 HTTPS,通过 localhost 访问我的 Web 应用和 Discourse,并将我的 Web 应用添加到 Discourse 的可嵌入主机和 CORS 主机名(http://localhost:8080),但我得到了相同的错误,只是主机不同:

在 'DOMWindow' 上执行 'postMessage' 失败:提供的目标源 ('http://localhost:3000') 与接收窗口的源 ('http://localhost:8080') 不匹配。

我运行的版本是 2.6.0.beta6 (60bc38e6a8),我是按照 Discourse for Mac 设置指南在几周前克隆 master 分支,并在今天运行 git pull origin master 获取的。

我还删除了 tmp 目录并重启了服务器。

我还出去散了散步,对着枕头大喊,并在桌下哭了一场。

希望这涵盖了所有方面。希望能有人提供帮助!

很抱歉听到您在设置过程中遇到了如此大的困难。

Discourse 的爬虫无法智能地抓取单页应用(SPA),所以这听起来最可疑。您能否尝试在一个包含静态内容的网站上复现此问题?

我们无法支持每一种定制化的安装方案,因此建议您进一步简化您的技术栈,直到它正常运行,然后再逐步添加其他组件。

感谢您的回复,别担心,我知道这一定会值得的!

我已经简化了架构,使用服务器端渲染(Rails)站点,并完全移除了 Nginx 层。我的应用运行在 3001 端口,Discourse 运行在 3000 端口。

我的嵌入代码渲染如下:

<script type="text/javascript">
      DiscourseEmbed = { discourseUrl: 'http://localhost:3000/',
                         discourseEmbedUrl: 'http://localhost:3001/enlistments/1' };
    
      (function() {
        var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
        d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
      })();
</script>

我已在 管理 > 自定义 > 嵌入 中将 localhost:3001 添加为可嵌入主机,并在 管理 > 设置 > CORS 中将 http://localhost:3001 添加为主机名。

错误依旧,但主机名已更新:

在执行 'postMessage' 时出错:提供的目标源 ('http://localhost:3000') 与接收窗口的源 ('http://localhost:3001') 不匹配。

现在的技术栈已经尽可能简单了 :thinking: 我猜这意味着是某种配置问题?有什么建议吗?

一些进一步的调试发现:

我手动创建了一个主题,并在我的 JavaScript 代码片段中将 discourseEmbedUrl: 'http://localhost:3001/enlistments/<%= @enlistment.id %>' 替换为 topicId: 14,结果评论成功加载了。这表明问题并非出在 CORS 或 X-Frame-上,而是:(a) 与网页抓取有关,(b) 可能与嵌入时的错误处理机制有关。

为了调查抓取问题,我访问了一个之前从未尝试访问过的新页面(因此理论上不应触发任何抓取操作)。我一边观察我的应用的 Rails 控制台,一边加载该页面。我在日志中看到了一次 /enlistments/6 的记录。我等待直到 JavaScript 控制台中抛出错误消息,此时 Discourse 本应已尝试抓取该页面,但我的应用的 Rails 控制台并未显示任何其他访问尝试日志。

在 Discourse 的 /logs 端点中也没有发现任何错误,在 Discourse 的 Rails 日志中我也看不出任何异常。

我起初以为可能是 Discourse 无法访问我的网站,于是我登录到 Discourse 应用的 Rails 控制台并执行了以下命令:

± |master U:3 ?:2 ✗| → rails c
Loading development environment (Rails 6.0.3.3)
[1] pry(main)> require "net/http"
=> false
[2] pry(main)> url = URI.parse("http://localhost:3001/enlistments/6")
=> #<URI::HTTP http://localhost:3001/enlistments/6>
[3] pry(main)> req = Net::HTTP.new(url.host, url.port)
=> #<Net::HTTP localhost:3001 open=false>
[4] pry(main)> res = req.request_head(url.path)
4=> #<Net::HTTPOK 200 OK readbody=true>
[5] pry(main)>

在我执行上述操作的同时,我在我的应用的 Rails 服务器访问日志中看到了相应的记录。这证实了 Discourse 确实能够访问我的应用。

现在我怀疑问题可能出在 Sidekiq 或任务调度上?:man_shrugging: 不过我不太清楚该如何调试这一点,因为我以前从未使用过 Sidekiq。

我又通过 TablePlus(一个支持 Redis 的数据库图形界面工具)查看了 Redis 中的数据,发现大约有 3 行数据,其 key 类似于 default:logster-env-96404aef1da0c422fc32e3bb82d85fbc,而 value 类似于:

[
  {
    "hostname": "myhostname",
    "process_id": 7188,
    "application_version": "60bc38e6a8914a10341a32ff9909e69faa65ffef",
    "params": {
      "embed_url": "http: //localhost:3001/enlistments/11927"
    },
    "HTTP_HOST": "localhost:3000",
    "REQUEST_URI": "/embed/comments?embed_url=http%3A%2F%2Flocalhost%3A3001%2Fenlistments%2F11927",
    "REQUEST_METHOD": "GET",
    "HTTP_USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.60 Safari/537.36",
    "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "HTTP_REFERER": "http://localhost:3000/embed/comments?embed_url=http%3A%2F%2Flocalhost%3A3001%2Fenlistments%2F11927",
    "time": 1606253787041
  }
]

其中 typeLISTttl-1。我猜这意味着任务已被触发?

我还在 /sidekiq 中查看了一番,但既没看到与此任务相关的任何记录,也没找到名为 RetrieveTopic 的队列 :frowning:

问题范围已经缩小了不少,但如果有什么想法,非常希望能得到您的帮助!

@eviltrout,既然我已经把设置简化到最基础的程度,对于进一步的故障排查有什么建议吗?

这几乎肯定是一个配置问题。也许您位于 localhost:3000 的 Discourse 实例认为它有不同的主机名。您可以在控制台中使用以下命令进行检查:

Discourse.base_url

另一个需要检查的是 /sidekiq 下的 Sidekiq 日志。

@eviltrout 我在 TopicRetriever 库中添加了若干调试代码,并确认 invalid_url? 返回 false(表明 URL 有效)。Discourse.base_url 确实已设置为 http://localhost:3000。我认为 RetrieveTopic 作业在某个环节静默失败了,我正在尝试定位具体位置。/logs 中没有错误日志,/sidekiq 中也没有任何关于主题检索的引用或日志记录。

抱歉,我目前没有其他想法了。我知道代码在生产环境中目前是正常运行的,所以问题可能出在环境、插件或配置上。

你好,感谢你的调查。我相信我遇到了完全相同的问题(嵌入功能在现有话题中正常,但在创建话题时失败)。在我的生产环境中这可以正常工作,但在我的开发机器上却不行。

我的环境是 Docker 堆栈,并且我已经确保 Discourse 和 Sidekiq 都能访问所有必要的服务。目前我开始怀疑,当 Discourse 尝试解析 URL 时(当 Onebox 尝试获取帖子中链接的预览时也会失败),它可能依赖于某个外部服务,而该服务无法访问本地实例……这种情况有可能吗?

@wilson29thid 从那之后你在自己的这边有什么发现吗?

我在生产机器上遇到了同样的问题,而不是开发机器。不过,我觉得用哪台机器其实并不重要。

不,恐怕我始终没能弄明白,最后只好手动调用 Discourse 的 API 来创建话题了 :pensive_face:

我可能也会这么做。好处是,你可以在人们开始评论你的文章之前,控制创建的线程数量。