SSE 与流式接口:从后端输出到前端体验
本文目录
Server-Sent Events 适用于服务器向浏览器单向持续推送文本事件:AI 输出、长任务进度、日志和导入结果。若客户端必须频繁向服务端发送实时控制或多人双向协作,再评估 WebSocket;协议选择应由交互方向和运维需求决定。
事件格式是公开契约
event: delta
id: run-42:17
data: {"seq":17,"text":"逐步输出"}
event: done
id: run-42:18
data: {"seq":18,"usage":{"outputTokens":321}}
响应使用 text/event-stream,事件字段按行书写、以空行结束。data 可有多行,客户端要合并;网络 chunk 边界不等于事件边界,一个 JSON 可能被拆成多个 chunk,也可能多个事件一起到达。UTF-8 decoder 应启用流式模式,事件解析要处理 CRLF/LF、注释心跳、未知字段、重复 id 和尾部未完成事件。
给每次生成定义 runId、单调序号、delta、状态、错误码与最终结果事件。断线恢复要回答服务端保留多久、Last-Event-ID 是否可恢复、重复事件如何去重;若不支持恢复,就明确让客户端新建任务或显示“连接中断,结果未确认”,不能静默把半截内容当成功。
后端连接生命周期
- 设置正确 MIME、关闭反向代理响应缓冲、避免压缩层攒住数据;按代理的空闲超时发送注释心跳。
- 发送 headers 后不能再用普通 JSON 状态码表示失败;后续错误必须以协议事件结束,并记录服务端日志。
- 监听客户端断开并取消模型、数据库游标或下游任务;AbortSignal 从请求一路传到实际工作层。
- 连接有并发上限、运行时长、输出 token/字节上限和每用户配额。断开连接不等同于任务取消,产品需明确是否继续后台运行。
- 认证凭证不要放 URL query,避免进入历史记录和代理日志;原生 EventSource 对自定义请求头支持有限,可使用受控 fetch stream 或短时一次性票据。
前端状态与体验
状态至少区分 idle、connecting、streaming、cancelling、completed、failed、disconnected。将已确认输出和缓冲中的片段分开,按帧或有界间隔合并 UI 更新,避免每个 token 都全量 Markdown 重排。用户停止后立刻更新按钮状态,并等待服务端确认取消或显示取消未确认。
服务端要区分“生成任务”和“浏览器连接”:若允许断线后继续生成,就持久化任务并提供有权限的状态查询;若断开即取消,就清理队列/模型资源并确认结果。重连时按序号去重,终止事件只能发生一次。配额按主体、并发任务和输出字节计算;连接数、任务总时长、首包时间和代理 idle timeout 分开设置。逐段测试代理/CDN 链路,不能只以本机直连顺畅作为验收。
流式 Markdown 对未闭合代码围栏、HTML 标签和表格要采用安全的增量策略:输出阶段可按纯文本呈现,完成后再用受信任的 Markdown renderer 渲染并净化。滚动跟随要允许读者手动滚离底部;页面隐藏时降低刷新频率但不丢事件。
诊断指标与测试
记录首事件延迟、事件间隔、完成率、断线率、取消生效率、输出字节、模型/下游耗时和 4xx/5xx。测试慢客户端、代理缓冲、短暂网络断开、重复 Last-Event-ID、UTF-8 多字节被拆分、服务端中途异常、用户切换账号、达到配额和客户端取消。
参考:WHATWG HTML Server-Sent Events、Node.js AbortSignal、Nginx proxy buffering。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。