Embeds: 加载卡住或主题未创建且无 logs,怎么办!

几天来,我注意到很多人搜索并发布了关于评论嵌入式内容的解决方案。我也是其中一员。我希望这篇帖子能帮助到处境相同的人。

我对 Discourse 比较陌生,所以任何希望用深入的专业知识来补充我提供的信息的人,请随时提出。

在我审阅了许多关于这个主题的帖子后,我可以肯定地说,问题的根源可能多种多样。对于像我一样的人来说,这里有一个解决方案!

问题

  1. 你发现嵌入式内容显示“正在加载讨论…”
  2. Discourse 主题没有自动创建

解决方案

尝试将你的域名添加到允许的内部主机列表中。

这是一个在管理区域中的站点设置。你可以在 Discourse 站点的此路径找到它们:

/admin/site_settings/category/all_results

我所指设置的直接链接是:

/admin/site_settings/category/all_results?filter=allowed_internal_hosts

对于那些查看 Rails 控制台的人,请查看:

SiteSetting.allowed_internal_hosts

该设置是由管道符(|)分隔的域名列表。

背景

我的 Discourse 实例是公开的,但我的内部 DNS 会在本地解析某些域名。在使用 Docker、Kubernetes 或任何具有内部 DNS 的环境的设置中都可能发生这种情况。

作为 Discourse 的新手,我必须说,现在看来显而易见的事情,一开始确实并不明显。

我们这些不熟悉 Discourse 内部机制的人并不知道,在 2017 年实施了 SSRF 保护,甚至不了解该保护的具体细节。只有回顾时,那个公告才使联系变得清晰。

这是一个实现良好的功能,但由于一个非常简单的原因,它却是一个令人费解的难题。

你必须知道什么

如果域名解析为本地 IP,Discourse 将不会为你的嵌入式内容创建主题。

各位,别急着抱怨。这是好事。你可以阅读有关 SSRF 的信息,了解原因,并感谢 Discourse 开发人员认真对待这个问题。

问题在于,Discourse 没有提供反馈,让我们知道为什么它没有创建主题,为什么它一直卡在“正在加载讨论…”的状态。

附加阅读

但是,本地 IP 究竟是什么?对于任何有兴趣的人来说,你可以在 Discourse 代码中找到答案,这里是直接指向 GitHub 上文件的链接

例如,如果你的 Discourse 实例 super-forum[dot]com 位于也托管 cool-blog[dot]net 的网络上,你的内部 DNS 可能会将 cool-blog[dot]net 解析为本地 IP — 除非将其列入白名单,否则 Discourse 将会拒绝。

希望这篇帖子能为其他人节省几个小时的绞尽脑汁的时间 — 甚至可能节省几根头发。

2 个赞

当我今天回到工作岗位时,有几件事在管理页面上引起了我的注意。以下是我对可以改进之处的一些想法。

嵌入设置 — /admin/customize/embedding/settings

allowed_internal_hosts 是在非公共环境中可靠嵌入的关键设置。它应该在此部分明确列出为相关设置——它的重要性毋庸置疑

嵌入主机 — /admin/customize/embedding

提供的配置代码片段非常有帮助,因为它包含了许多有用的信息。我认为我们可以利用这些信息提供额外的指导。

第一段

当前:

将以下 HTML 代码粘贴到您的网站中,以创建和嵌入 Discourse 主题。将 EMBED_URL 替换为您正在嵌入的页面的规范 URL。

替代:

将以下 HTML 粘贴到您希望评论显示在页面上的位置。
discourseEmbedUrl 是您页面的 URL——将从 Discourse 中链接到该页面。当您的页面首次加载时,Discourse 将尝试查找或创建一个与该 URL 相对应的主题,并链接回您的内容。

第二段

当前:

如果您想自定义样式,请取消注释并用主题的嵌入式 CSS 中定义的 CSS 类替换 CLASS_NAME

替代:

使用 className 属性为嵌入式 iframe 中的 <html> 标签添加自定义类。 要对其进行样式设置,请转到 /admin/customize/themes,点击您主题的编辑按钮,然后点击编辑代码按钮,并勾选显示高级设置。将您的自定义 CSS 添加到嵌入式 CSS 部分。

第三段

当前:

DISCOURSE_USERNAME 替换为应该创建主题的作者的 Discourse 用户名。Discourse 将通过 name 属性设置为 discourse-usernameauthor<html> 标签的 content 属性自动查找用户。discourseUserName 参数已被弃用,将在 Discourse 3.2 中删除。

替代:

注意:该主题是由一个真实的 Discourse 用户创建的——而不是一个显示名称或作者字符串。它必须是一个有效且存在的帐户。有三种方法可以确定使用哪个用户

  1. 默认回退——在 /admin/customize/embedding/posts_and_topics 中设置
  2. 每个主机的覆盖——在 /admin/customize/embedding/ 下设置
  3. 每个 URL 的控制——在您的页面上添加一个 <html> 标签,其中包含一个存在的 Discourse USERNAME <meta name="discourse-username" content="USERNAME">

只有现有的 Discourse 用户的用户名才有效。如果找不到元标签用户,Discourse 将回退到主机级别或全局默认设置。此处所示的 <html> 标签方法允许对用于创建主题的 Discourse 用户进行编程的、每个 URL 的控制。例如,您可以将您网站上的博客文章作者映射到匹配的 Discourse 用户帐户。

“配置代码片段”部分

可折叠的“配置代码片段”部分很容易被忽略。在视觉上,它类似于一个标题,而微妙的箭头并不直观。与有颜色且引人注目的“了解更多”链接不同,这个链接感觉像是隐藏在显眼之处。

我知道这可能存在争议——有些人可能觉得 UI 干净且足够。但我个人在意识到这个部分是可点击的之前,已经错过了很多次。这告诉我,即使只是轻微的,也可以改进其可发现性。更清晰的视觉提示或默认展开可能会有很大帮助,特别是对于依赖示例的新用户。

代码片段

当前:

<div id='discourse-comments'></div>
  <meta name='discourse-username' content='DISCOURSE_USERNAME'>

  <script type="text/javascript">
    DiscourseEmbed = {
      discourseUrl: 'https://discourse.your-site.com/',
      discourseEmbedUrl: 'EMBED_URL',
      // className: 'CLASS_NAME',
    };

    (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>

替代:

<div id="discourse-comments"></div>
<!-- Optional: specify which Discourse user account creates the topic -->
<!-- If omitted, Discourse falls back to the per-host or global default user -->
<meta name="discourse-username" content="DISCOURSE_USERNAME" />

<script type="text/javascript">
  DiscourseEmbed = {
    discourseUrl: 'https://discourse.mydomain.com/', // Trailing slash required
    discourseEmbedUrl: window.location.href, // Or a hardcoded canonical URL string
    // className: 'my-iframe-theme another-class',
    // discourseReferrerPolicy: 'strict-origin-when-cross-origin',
    // topicId: '1234',
  };

  (function() {
    const d = document.createElement('script');
    d.type = 'text/javascript';
    d.async = true;
    d.src = `${DiscourseEmbed.discourseUrl}javascripts/embed.js`;
    document.head.appendChild(d);
  })();
</script>

DiscourseEmbed 选项——简短描述

  • discourseUrl必需
    您的 Discourse 实例的完整 URL。必须以斜杠结尾,例如 https://discourse.mydomain.com/

  • discourseEmbedUrl必需
    正在嵌入评论的当前页面的完整 URL。这是 Discourse 识别和链接主题到您的内容的方式。

  • className可选
    将自定义 CSS 类添加到iframe 内<html> 元素。在 Discourse 主题的“嵌入式 CSS”部分定义样式。

  • discourseReferrerPolicy可选
    默认为 no-referrer-when-downgrade。参见:Referrer-Policy

  • topicId可选
    如果设置,Discourse 将直接使用此主题。否则,它将查找与 discourseEmbedUrl 匹配的主题,如果不存在则创建一个。

DiscourseEmbed 上下文文档

discourseEmbedUrl 必须可供您的 Discourse 服务器访问。当 iframe 加载时,Discourse 会获取该 URL 的页面以创建或定位主题。这对于托管在公共平台上的网站来说可以无缝工作。

然而,如果您在本地开发,嵌入可能会卡在“正在加载讨论…”或根本不显示。这是因为本地开发 URL(如 localhost)可能会被 Discourse 的 SSRF 保护force_https 选项或缺少 可嵌入主机 阻止。

如果您正在为现有网站构建新功能,一种解决方法是将 discourseEmbedUrl 指向生产 URL。当启用 embed_any_origin 时,即使 iframe 来自不同的源,Discourse 也允许嵌入正常工作。如果评论存在,它们将加载;否则,将显示“继续讨论”按钮。

或者,如果您的本地域(例如 localhost)已在 嵌入主机 下添加,您可能根本不需要 embed_any_origin。但您仍然需要将 localhost 添加为可嵌入主机

:warning: 一个注意事项:如果启用了 force_https 设置,而您的开发站点未使用 TLS,则嵌入将失败。在这种情况下,请在开发期间禁用 force_https,或考虑启动一个单独的 Discourse 实例进行测试。

**注意:**如果 discourseEmbedUrl 是公开可访问的,并且嵌入仍然显示“正在加载讨论…”而没有创建主题,您的域可能被 Discourse 的 SSRF 保护阻止。

这通常发生在您的 Discourse 实例运行在具有本地 DNS 解析的环境中时——例如 Docker、Kubernetes 或带有内部 DNS 服务器的 LAN。在这些情况下,Discourse 可能会将您站点的域解析为本地 IP 地址(例如 127.0.0.1192.168.x.x)并将其视为不安全。

要允许访问,请将您的域添加到 allowed_internal_hosts 站点设置中。这会明确标记您的域为可安全抓取,从而绕过 SSRF 过滤。

被阻止的 IP 地址范围的完整列表可在 Discourse 的源代码 中找到。

允许的内部主机——[站点设置说明]((/admin/customize/embedding/settings`)

当前

Discourse 可以安全抓取的内部主机列表,用于 oneboxing 和其他目的

替代

允许 Discourse 抓取解析为内部 IP 的主机。当您的站点在本地 DNS(例如 Docker、LAN、Kubernetes)后面运行时需要。当 SSRF 保护否则会阻止访问时,对于评论嵌入、主题创建和 oneboxing 是必需的。

考虑因素

其中一些建议可能更适合作为指向官方文档的链接。事实上,这篇帖子本身可能就能完成这项工作,因为它应该像其他帖子一样被索引。其他一些建议可能需要一个正式的拉取请求——我最终可能会完成,但不是今天。

尽管如此,我所写的内容可能存在技术上的不准确之处。大部分内容来自实践经验,但我可能误解了某种行为,被缓存(哦,缓存……)所迷惑,或者仅仅是疏忽了某些东西。在这方面,我将听取资深的 Discourse 专家的意见。