流式输出:SSE 与 WebSocket
流式输出用 SSE 就够:单向、HTTP、EventSource 原生支持;WebSocket 只在需要客户端中途插话(语音、协同编辑)时才必要。SSE 的五个坑都写在规范里 —— 代理缓冲、超时、重连、事件语义三家不兼容、浏览器端带不了 Authorization 头所以绕不开自建后端。
也叫:流式输出 · streaming · SSE · Server-Sent Events · WebSocket · EventSource · 代理缓冲
二、SSE 的五个坑(都写在规范或官方文档里)出自 T6-2
坑 1:EventSource 不能设请求头。 它的构造器只接受 url 和 { withCredentials },没有 header 参数,所以带 Authorization 的 API 永远不能用它直连。实践上一律改成 fetch() + ReadableStream 自己解帧,代价是规范白送的自动重连、Last-Event-ID 续传、retry: 退避全要自己重写。
坑 2:代理缓冲。 就是开头那个故障。nginx 的 proxy_buffering 默认 on;上游可发 X-Accel-Buffering: no 关掉,但这个头可能被网关的 proxy_ignore_headers 吃掉。
坑 3:空闲超时。 nginx 的 proxy_read_timeout 默认 60 秒,算的是"两次成功读取之间"。模型长时间思考不吐字,60 秒后连接就被切——表现是"流到一半断"。规范给的解法很明白:每 15 秒左右发一个注释行当心跳。
坑 4:HTTP/1.1 的 6 连接限制。 浏览器对同一域名最多开 6 个连接,SSE 长期占用一个;开到第 7 个标签页就永远连不上。Chrome 和 Firefox 都标成 Won't fix。HTTP/2 下不再是问题——变成协商的并发流,默认上限 100。
坑 5:失败与重连的区别。 规范规定网络错误才自动重连;状态码不是 200、或 Content-Type 不是 text/event-stream,属永久失败,不重连。所以后端返回 500 时 SSE 是"静默死掉"的,前端必须自己兜。
以上节选自T6-2 流式与实时链路:从 SSE 的坑到语音的 200 毫秒,读全文能看到前后语境。