背景我正在构建一个使用 HTTP/1.1 供内部使用的 Web 应用程序。它没有 TLS,因此不支持 HTTP/2,也不支持 HTTP/3 或 QUIC(这些新协议可以解决问题,但
我正在构建一个使用 HTTP/1.1 供内部使用的 Web 应用程序。它没有 TLS,因此不支持 HTTP/2,也不支持 HTTP/3 或 QUIC(这些新协议可以解决问题,但目前无法访问)。
客户端使用:
所有请求均由 nginx 提供服务,但看起来我的问题与它无关。
有时,刷新页面后,某些请求会停滞数秒。这些请求是由 Chrome 停滞的,即 Chrome 决定在导航或 JS Fetch API 发起请求后不立即发送 HTTP 请求。DevTools 没有给出任何提示来了解为什么会发生这种情况。
我尝试排除一些常见问题:
{cache: 'no-store'}
指定
经过一些检查后,我花了一些时间将 nginx 配置为 Connection: close
在所有响应中始终发送 HTTP 标头,这样客户端就不会尝试对即将到来的请求重用相同的 TCP 连接。我认为 Chrome 乐观地拖延重用 SSE 请求的 TCP 连接,假设它会在短时间内释放。这对于防止连接重用是有效的,但这不是问题的根源——Chrome 中没有这种乐观的拖延,这是有原因的。
进入Wireshark
并发 SSE 连接实际上已达到 6 个连接的限制。
重点是:这些 SSE 连接未被任何选项卡使用,未显示在 Dev Tools 中,并且底层 TCP 保持打开状态。事实上,Wireshark 允许我查看 TCP 4 次终止握手:
更准确地说,捕获的 TCP 数据包的序列是(见下面的截图):
上述事件发生在 1.2 毫秒内,因此在我看来,Chrome 决定暂停流 13 的请求,直到流 1 连接的客户端>服务器端关闭。经过进一步检查,我还可以确认关闭流 1 确实释放了 第六个连接 (打开的 TCP 连接数从 6 个减少到 5 个,允许为暂停的请求打开新连接)。(我省略了以下 ACK 和上述数据包之后流 13 的完全终止)。
为什么 SSE 连接仍然处于打开状态?
如何避免僵尸 SSE 连接?
为什么开发工具中没有报告僵尸连接?
如何避免僵尸 SSE 连接?
解决方案:卸载之前明确关闭服务器发送事件源:
const sse = new EventSource(...)
const handler = () => {
sse.close()
}
window.addEventListener('beforeunload', handler)
window.addEventListener('unload', handler)
我通过反复注释处理程序将其作为解决方案隔离开来,并确认当且仅当我明确关闭 SSE 连接时,它们才会在页面重新加载时始终关闭。
但是, HTTP/1.1 上 SSE 的已知限制 在我看来并不是在页面卸载后留下僵尸 SSE 连接的好借口。DevTools 报告 SSE 请求已完成(瀑布列中的条形图不会继续增长),而它实际上仍然在与页面分离的情况下运行。