在Cloudflare后面无限加载

我们已将服务器从一个 VPS 提供商迁移到另一个,并通过 launcher rebuild 将实例升级到了最新版本,从 3.5.0.beta3 升级到了 3.5.0.beta4

实例之前在 Cloudflare 后面运行一直正常,但现在访问它会导致无限的 5 个点的加载动画。

我的本地系统有一个 hosts 文件条目可以绕过 Cloudflare,因为我的 ISP (Deutsche Telekom AG) 有糟糕的对等策略,有时通过 Cloudflare 的访问速度非常慢。所以起初我没有注意到这个问题,因为绕过 Cloudflare 访问一切正常。然后我升级了实例,因此现在不确定是更改 VPS 还是 Discourse 升级是相关的更改。我通过 VPN 和移动网络确认了问题确实是 Cloudflare 本身,而不是我 ISP 的糟糕对等连接,而且其他用户也面临同样的问题。旧 VPS 和新 VPS 都有 IPv6,整个系统完全相同,作为原始映像文件传输。

没有任何错误消息,无论是浏览器(控制台)、主机系统的代理、容器内的 Nginx,还是 Rails 或其他任何地方。HTML 文档和几个脚本加载正常,与绕过 Cloudflare 时提供的脚本进行比较,发现(我检查过的)一切都是相同的。响应头看起来也大多相同,当然,除了少数 Cloudflare 特定的头。我看到的最后加载的东西是 mini profiler:

当然,清除浏览器缓存、使用隐私窗口等都没有改变任何东西。清除/禁用 Cloudflare 缓存也没有帮助,所以缓存不是问题。我暂时完全禁用了整个论坛的 CF 缓存。

值得一提的是,论坛运行在主机上的 Apache 代理的子路径后面,遵循以下说明:Serve Discourse from a subfolder (path prefix) instead of a subdomain
以前,我们只是创建了一个 ln -s . forum 符号链接而不是 uploads/backups 符号链接,并重复了说明中的重写规则,这在多年来(以及现在没有 Cloudflare 的情况下)一直运行良好,但作为我调试工作的一部分,我切换到了这些说明,以确保内部代理应用所有规则。信任的头是 CF-Connecting-IP,尽管我也启用了 cloudflare.template.yml,即使它在某种程度上是重复的。我还尝试来回更改这些模板和上述说明的各个部分,也试图检查代理 IP 头是否会产生任何差异,因为绕过 Cloudflare 时缺少 CF-Connecting-IP 是一个问题。

此时我已完全没有想法了,没有一丝线索表明问题可能来自哪里,也没有任何相关的日志/输出。通过 Cloudflare,Discourse 只是卡在加载动画中,没有进一步的线索。

我希望有人能想到调试此问题的方法,或者 3.5.0.beta33.5.0.beta4 之间是否存在可能相关的更改。我猜降级会有问题?

这是实例:https://dietpi.com/forum/
编辑:我现在已经禁用了 Cloudflare。但有一个 CNAME 仍然通过 Cloudflare 传递,所以这两个可以进行比较:https://www.dietpi.com/forum/

有趣的问题。

它只是挂在永远的 https://www.dietpi.com/forum/

$ wget https://www.dietpi.com/forum/
--2025-05-03 10:52:18--  https://www.dietpi.com/forum/
Resolving www.dietpi.com (www.dietpi.com)... 104.21.12.65, 172.67.193.183, 2606:4700:3035::6815:c41, ...
Connecting to www.dietpi.com (www.dietpi.com)|104.21.12.65|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: âindex.html.1â

    [<=>                                                          

有趣的是,像 https://www.dietpi.com/forum/site.json 这样的调用确实成功了。

https://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355 不起作用并且永远挂起,但是
https://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355.json 可以。

1 个赞

确实很有趣。我刚注意到 HTML 文档没有完全加载,而是在某个点停止了。我比较了两种情况下的 /forum/,认为它们是相同的,但可能我太专注于头部了,而底部的一些正文部分却丢失了。

通过 Cloudflare 加载时最后一行:

      <discourse-assets-json>
        <div class="hidden" id="data-preloaded" data-preloaded="{&quot;topic_list&quot;:&quot;{&quot;users&quot;...&quot;:false,&quot;allowed_iframes&quot;:&quot;https://dietpi.com/forum/discobot/certificate.svg

由于远远超过了帖子的字符限制,因此需要截断。文档通常会继续如下:

      <discourse-assets-json>
        <div class="hidden" id="data-preloaded" data-preloaded="{&quot;topic_list&quot;:&quot;{&quot;users&quot;...&quot;:false,&quot;allowed_iframes&quot;:&quot;https://dietpi.com/forum/discobot/certificate.svg&quot;,&quot;can_permanently_delete...

其他页面也在同一点停止。我认为我们在这里发现了一些东西。

编辑:啊,等等,我检查错了,其他页面在别处停止了。所以不是这个特定的 HTML 元素/属性。

是的,每个页面/HTML 文档通过浏览器加载时,都会一次又一次地在同一个字符处停止,包括在隐私窗口中。但是不同的页面会在不同的点停止。通过 curl 加载时,它们也总是在同一点停止,但会是另一个点,而 wget 又总是会在一个相同的点停止,但会是另一个略微不同的点。非常奇怪。

您是否启用了某些优化?

没有,没有(内容)优化。我确实启用了 103 个早期提示功能,但已经禁用了它,试图解决问题。尝试了协议设置,但也没有改变任何东西:

顺便说一句,没有 content-length 响应头,这会引起问题吗?我的意思是,绕过 Cloudflare 时它也不存在,但可能是 Cloudflare 的问题?编辑:不,动态页面似乎是正常的,我们的 Wordpress 和 Matomo 页面也是如此,但它们不会引起任何问题。

在玩 curl 时又发现了一个问题。打印到 STDOUT 会显示完整的 HTML 文档,但仍然挂起,最后是:

  <p class='powered-by-link'>Powered by <a href="https://www.discourse.org">Discourse</a>, best viewed with JavaScript enabled</p>
</footer>


  </body>

</html>

但是当尝试通过 -o 或简单的重定向保存它,甚至只是通过 grep 管道传输它时,它会在不同的位置挂起:

            <div class="link-bottom-line">
                <a href='/forum/c/general-discussion/7' class='badge-wrapper bullet'>
                  <span class='badge-category-bg' style='background-color: #F7941D'></span>
                  <span class='badge-category clear-badge'>
                    <span class='category-name'>General Discussion</span>
                  </span>
                </a>

并且当我访问 https://www.dietpi.com/forum/ 时,使用 curl 而不是立即将其打印到控制台,我可以 100% 重现这个相同的 73728 字节。这太奇怪了 :face_with_monocle:


所以:

  • 所有客户端在加载我们 Discourse 实例的任何 HTML 文档时都会挂起。
  • 每个客户端在加载同一页面时都会在同一个字节处挂起。
  • 不同的客户端在不同的点挂起,但在使用同一客户端重复时,在同一个字节处挂起。
  • 每个页面在文档的不同点和不同的下载大小处挂起。
  • 相同的工具 curl 在仅打印到 STDOUT 与管道传输或将文档存储在某处时,在不同的点挂起。
  • wget 能够将完整文档(至少是 https://www.dietpi.com/forum/)下载到文件,但仍然在末尾挂起,当 curl https://www.dietpi.com/forum/ 将完整文档打印到控制台时也是如此,但在末尾挂起。

我认为这可能是缓冲问题。但在调查时,我注意到了一些其他事情。

wget -O - https://www.dietpi.com/forum/latest

以以下内容结束

  </body>
</html>

但连接从未关闭。

理论:某个地方存在配置问题,HTTP 版本或标头(如 keep alive 连接)不匹配,当文档大于 X(我怀疑是 64KB)时,这才会成为问题。

是的,wget 始终会下载整个文档,而 curl 在直接打印到控制台时也会这样做,但连接不会关闭。对于像我测试过的关于只有一个帖子的 14k 主题这样的小型文档,也会发生同样的情况。但即使是较小的文档,当通过管道传输或存储到文件时,curl 通常也不会完全下载,浏览器也不会。

两个工具都始终显示 HTTP/2,并且我在 Cloudflare 中启用了 HTTP/2 源请求。但值得尝试显式使用其他 HTTP 版本。昨天我禁用了上面截图中的所有 Cloudflare 协议设置,但没有帮助。但我会再试一次。我还可以启用服务器上的访问日志,以查看来自 Cloudflare 的实际传入请求。

我尝试了所有支持的 HTTP (1.1-3) 和 TLS (1.2-1.3) 版本组合,但没有区别。我还禁用了 HTTP3 支持,HTTP2 源请求再次出现 0-RTT 连接恢复。没有区别,curl 仍然卡在 https://www.dietpi.com/forum/ 的确切 73,728 字节处。

关于文档大小过大的理论,https://www.dietpi.com/dietpi-software.html 有 199,475 字节,加载完美。我应该提到服务器(相同的网络服务器)托管静态网站、MkDocs 实例、WordPress、Matomo,所有这些都运行完美。还有一个 Grafana 实例,前端网络服务器通过 UNIX 套接字作为代理。

但我同意这似乎与缓冲区或块大小有关。奇怪的是,直到挂断的下载大小在客户端和页面之间差异很大,而尽管更改了协议版本,它却保持完全相同,并且即使在文档完全下载后连接也没有关闭。就像停止信号丢失了一样,尽管我在这方面缺乏对 HTTP 的见解。因此,我考虑了 content-length 标头,但显然它不是必需的。

网络服务器还通过 UNIX 套接字充当 Discourse 容器的代理。我可以启用 TCP 侦听器,使 Discourse 实例无需代理即可额外可用(当然,会保留容器内的 Nginx)。

您能否在 Apache 中尝试 KeepAlive Off

我猜这至少可以排除 Web 服务器的嫌疑,所以值得一试。

1 个赞

没有变化。同样来自 Apache 文档:

此外,只有在内容长度可预知的情况下,才能使用与 HTTP/1.0 客户端的 Keep-Alive 连接。

因此,由于缺少 content-length,它可能不适用于此请求。

由于这需要重新构建,我将在我们的常规网站活动最少的时候稍后进行。嗯,我只是在考虑 HTTPS……看起来我需要对内部 Nginx 配置进行一些自定义调整,以保持 UNIX 套接字功能以及纯 HTTP 连接,同时监听一个额外的端口用于 HTTPS(带有来自主机的 TLS 证书),但没有 HTTPS 重定向/强制。……另外一个纯 HTTP TCP 端口也会很有趣,适用于可以忽略 HSTS 的客户端。

您是否在使用 CloudFlare 的 RocketLoader?我知道它与其他脚本一起会导致问题。

另外,您是否清除了 CF 缓存?

您是否在使用 CF 上的入站规则,这些规则可能与您旧的 VPS IP 地址相关联而未更新到新的 IP 地址?

1 个赞

无 RocketLoader:请注意,根据上述使用 curlwget 的测试(它们不解释任何语法,因此不加载任何 JavaScript、样式或其他任何内容),问题在于原始 HTML 文档的下载始终挂起。

Cloudflare 缓存未激活于论坛,原始 HTML 文档从未被缓存。

无 VPS 特定规则。通常不对论坛设置规则,除了绕过缓存。问题在两种情况下都出现,因此缓存也不是问题所在。

1 个赞

在测试绕过 Discourse 容器主机上的 Apache2 代理并禁用 Cloudflare 上的强制 HTTPS 重定向以通过 curl 测试纯 HTTP 连接时,我终于在 Cloudflare 上找到了罪魁祸首:

我不确定我们的 VPS 切换和/或 Discourse 从 3.5.0.beta3 升级到 3.5.0.beta4 以及/或者同时在 Discourse 上发生了什么变化,但似乎 Discourse 的 HTML、CSS 或 JavaScript 文档中的某些内容导致 Cloudflare 对嵌入式 URL 的 HTTPS 重写出现问题。看起来不完整和挂起的 curl 请求实际上并没有关联,或者可能有关联。奇怪的是,在浏览器网络选项卡中可以看到 HTML 文档的不完整内容,就好像 HTTPS 重写功能在流经文档时执行了此操作一样。

是否有人有实例和 Cloudflare 帐户可以对此进行测试,看看这是一个普遍问题还是与我们的特定实例/设置有关?

顺便说一句,要测试绕过代理以及 HTTP,同时保持通过代理的连接处于活动状态,像这样在容器内手动调整 Nginx 配置可以完美运行:

root@dietpi-discourse:/var/www/discourse# cat /etc/nginx/conf.d/outlets/server/10-http.conf
listen unix:/shared/nginx.http.sock;
set_real_ip_from unix:;
listen 8080;
listen [::]:8080;
listen 8443 ssl;
listen [::]:8443 ssl;
http2 on;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

ssl_certificate /shared/fullchain.cer;
ssl_certificate_key /shared/dietpi.com.key;

ssl_session_tickets off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:1m;

当然,重要的是要删除 HTTPS 重定向和 HSTS 标头,并公开添加的端口。

另外一个发现:我们使用 mod_sed 将 Matomo 跟踪代码添加到所有 text/html 响应中,就在 </head> 结束标签之前。禁用 Discourse 的 mod_sed(或绕过 Apache2 代理)也能解决问题,尽管 Cloudflare 自动 HTTPS 重写是激活的。禁用其中任何一个都能解决问题。在所有其他页面上,这种组合都能正常工作,即使是我们非常大的页面,也比失败的论坛页面要大。所以,也许是两个过滤器,首先是我们的代理上的 mod_set,然后是 Cloudflare 嵌入的 URL 重写,导致某些东西中断,这与文档或块大小或其他什么有关。

我们现在通过 Discourse 主题编辑嵌入跟踪器,并且我还禁用了 Cloudflare 自动 HTTPS 重写。我们的整个网站上没有混合内容。如果有,最好能看到并修复它,而不是让 Cloudflare 永远隐藏它。

我相当确定那行不通。

我不确定您要解决什么问题,但您可能需要在 app.yml 中启用 force_https

4 个赞

我猜想仅从“Cloudflare 自动 HTTPS 重写”这个名称就可以产生误解。Cloudflare 有 2 个功能:

  • “始终使用 HTTPS”会将所有纯 HTTP 请求重定向到 HTTPS,就像 Discourse 中的 force_https 一样。两者之前都已启用,我禁用了两者以测试 HTTPS 是否与问题或 Discourse 的无尽加载页面和挂起的 curl 请求有关。这效果非常好,甚至解决了 HTTPS 请求的整个问题,但这仅仅是因为我在同一次操作中禁用了“Cloudflare 自动 HTTPS 重写”。
  • “Cloudflare 自动 HTTPS 重写”会修改 HTML、CSS 和 JavaScript 文档,用 HTTPS 变体替换所有嵌入的纯 HTTP URL,Cloudflare 认为主机可以通过 HTTPS 访问(基于 HSTS 预加载列表等)。这是为了避免混合内容警告。

在 Cloudflare、主机代理或 Discourse 中强制或不强制 HTTPS 都没有关系。导致问题的是主机代理中的 mod_sed 过滤器与 Cloudflare 嵌入的纯 HTTP 编辑的组合。因此,文档内容通过了两个阶段的过滤器。问题不在于实际内容更改(我们的网站上没有任何混合内容,“Cloudflare 自动 HTTPS 重写”因此实际上并未更改文档正文),而可能与块、缓冲区或计时有关。

1 个赞

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.