服务端持续推送数据,SSE究竟怎样工作
任务执行进度、通知列表、逐段生成的文字,都有一个共同点:服务器会持续产生新数据,浏览器需要及时显示。如果每隔一秒发一次查询请求,既会产生空轮询,也很难让更新足够连贯。
这类以服务器向浏览器推送为主的场景,可以考虑 SSE,也就是 Server-Sent Events。浏览器发起一个 HTTP 请求,服务器保持响应打开,并按规定的文本格式不断写入事件。它主要解决单向推送;客户端需要提交指令时,仍然可以使用普通 HTTP 请求。
SSE 响应使用 text/event-stream,内容按 UTF-8 编码。一个事件以空行结束,格式可以是:
id: 1042
event: update
data: {"text":"任务已经完成一半"}
其中 event 指定事件类型,data 是数据,id 是服务器分配的事件标识。一个事件可以有多行 data,浏览器会把它们用换行连接。不要把任意一次网络读取到的字节块当成完整事件:网络分块可能拆开一行,甚至拆开一个中文字符。
如果使用浏览器内置的 EventSource,这些解析工作由浏览器完成:
const stream = new EventSource('/api/events');
stream.addEventListener('update', event => {
try {
const value = JSON.parse(event.data);
if (typeof value.text === 'string') {
document.querySelector('#progress').textContent = value.text;
}
} catch {
console.warn('收到无法解析的推送数据');
}
});
stream.onerror = () => {
// 临时断线时通常会自动重连,不要在这里反复创建新连接。
console.warn('推送连接异常,等待恢复');
};
// 页面组件销毁或用户结束订阅时调用 stream.close()。
示例假定页面存在 progress 元素,且服务端确实发送 update 类型事件。如果没有指定事件类型,应通过 message 监听。某些任务的“业务错误”也需要单独定义事件,不能把连接层的异常和任务执行失败混为一谈。
EventSource 具有自动重连能力,但自动重连不等于自动恢复所有数据。在符合重连条件时,浏览器会尝试恢复连接;已有事件标识时,会通过 Last-Event-ID 传给服务端。服务器必须维护可追溯的事件序列,并根据这个标识补发遗漏内容,断线期间的数据才有机会恢复。
事件历史不能无限保存,因此还需要约定过期处理。例如客户端提供的标识已经早于保留范围,服务端可以发送“需要重新同步”的业务事件,让页面重新取得完整状态。客户端也应能按事件标识处理可能出现的重复内容,不能默认每条消息只会出现一次。
在网关或反向代理之后,还要检查响应缓冲和空闲超时。应用程序虽然逐段写入,代理如果一直攒着不发,浏览器看到的仍然会是一大段延迟结果。可以根据实际服务器配置关闭相关缓冲,并定期发送以冒号开头的注释心跳。心跳能减少空闲断开,但无法保证绕过所有平台的连接时长限制。
身份验证也有约束。原生 EventSource 主要发起 GET 请求,不能像 fetch 一样随意添加自定义认证头。可以按业务使用安全的 Cookie 会话;跨来源携带凭证时,还要正确配置 CORS 和 Cookie 策略。不要为了方便就把长期访问令牌放到 URL 中,它可能进入日志和历史记录。
验证时,应检查响应是否真正逐段到达,再主动断网,观察恢复后是否缺消息、是否重复。还要通过真实网关测试,而不仅是在本地直连应用。SSE 的接口很短,可靠使用却依赖事件边界、连接管理和服务端补发这几件具体工作。
- 点赞
- 收藏
- 关注作者
评论(0)