伴随实时 Web 的兴起,开发者越来越需要一种开销小、部署简单、又不失可靠性的推送机制。text/event-stream
正是在这种需求背景下应运而生的 MIME 类型:浏览器通过 EventSource 接口发起一次普通 HTTP 请求,服务器按 text/event-stream
规范持续将事件块写回,连接得以常驻,数据即可源源不断地单向流入页面。它借助现成的 HTTP 基础设施避开了复杂握手,却依旧能提供类似 WebSocket 的实时体验。下文从协议细节、浏览器实现、最佳实践到性能安全全景展开,配合实际案例与对比分析,力求为你呈现一个可落地、可扩展、可维护的 text/event-stream
世界。
背景与概念
HTML Standard 将 Server-Sent Events(简称 SSE)定义为浏览器端 EventSource 接口与服务器端事件流格式的组合;事件流内容的官方 MIME 类型即 text/event-stream
,并要求 UTF-8 编码(HTML Standard, HTML Standard)。当浏览器检测到响应头 Content-Type: text/event-stream
,便会把接收的数据按照行分隔规则解析成离散事件,而无需二次解析 JSON 或自定义协议。因此,text/event-stream
并非一门新协议,而是对现有 HTTP 的最小增量扩展。
与传统的 Ajax 轮询相比,SSE 只创建一次 TCP 连接并重用该连接输送所有更新,显著降低了握手开销和延迟(MDN Web Docs, MDN Web Docs)。与 WebSocket 的双向信道不同,SSE 仅支持服务器向客户端单向推送,但它的部署对反向代理、防火墙、CDN 更友好,因为报文保持纯文本 HTTP 格式(Ably Realtime, Ably Realtime)。这使得 text/event-stream
在需要浏览器频繁接收而很少发送数据的场景——例如监控面板、价格行情、日志流——展现出极高的性价比。
事件流格式与工作机制
请求阶段
浏览器通过 new EventSource('/stream')
创建连接时,会自动在请求头加入 Accept: text/event-stream
,表明只接受符合事件流规范的响应(MDN Web Docs)。服务器若支持 SSE,应返回状态码 200 或 206,并写入 Content-Type: text/event-stream
以及可选的 Cache-Control: no-cache
,随后立即 flush 输出缓冲区,让客户端尽早进入就绪态。
数据块语法
一条事件消息由若干行键值对组成,每行格式为 field:value\n
,消息末尾以额外空行结束。常用字段包括:
-
data:
事件载荷,多行data:
会被合并并以\n
连接。 -
event:
自定义事件名,对应EventSource
监听器的类型。 -
id:
递增唯一标识,浏览器会自动在后续请求的Last-Event-ID
头携带该值,便于断线续传。 -
retry:
服务器建议的重连间隔(毫秒)。
规范要求行分隔可以是 CRLF
、LF
或 CR
,并明确空行才是消息结束标志(HTML Standard)。
保活与自动重连
若连接意外断开,浏览器会按照上次成功接收的 retry
字段或默认 3 秒策略重连,并附带 Last-Event-ID
以实现至少一次送达语义(Stack Overflow)。服务器可通过发送 :comment\n\n
保活帧向代理声明连接活跃状态;该行以冒号开头,不产生任何事件,仅用于避免闲置连接被中间件关闭(Stack Overflow)。
浏览器兼容性与网络设施
自 Chrome 6、Firefox 6、Safari 5.1、Edge 79 起,主流浏览器均已对 EventSource 提供完整实现,唯独 IE 系列从未支持,该差距可通过 polyfill 或回退到长轮询策略弥补(Can I Use, LambdaTest)。由于 SSE 基于 HTTP/1.1 的分块编码,在 HTTP/2 环境同样可以工作;若部署在反向代理或 CDN(如 Nginx、Cloudflare)后,需要关闭缓冲或使用 proxy_buffering off
等指令,以免代理将事件批量缓存导致客户端收不到实时数据(Cloudflare Docs)。
与其他实时通信技术的对比
维度 | SSE (text/event-stream ) | WebSocket | Long Polling |
---|---|---|---|
连接方向 | 单向 | 双向 | 单向 |
建立成本 | 普通 HTTP | 升级握手 | 多次短连接 |
代理兼容 | 友好 | 取决于环境 | 友好 |
数据帧格式 | 简单文本 | 二进制帧 | 单独响应 |
应用典型场景 | 实时仪表盘、日志、通知 | 协同编辑、游戏 | 低频刷新 |
文章选择的对比维度来自 Ably 系列研究与社区实测数据(Ably Realtime, Ably Realtime, Ably Realtime, Ably Realtime, Ably Realtime),展示了 text/event-stream
在单向低复杂度推送场景的优势。
实战:用 Node.js Express 构建股票价格推送
下面示例演示如何在 20 行左右代码内完成可生产的 SSE 服务。所有双引号已替换为反引号以遵守排版要求。
server.js
import express from `express`
const app = express()
const clients = new Set()
app.get(`/prices`, (req, res) => {
res.writeHead(200, {
`Content-Type`: `text/event-stream`,
`Cache-Control`: `no-cache`,
Connection: `keep-alive`
})
res.write(`:\n\n`) // 立即 flush
clients.add(res)
req.on(`close`, () => clients.delete(res))
})
setInterval(() => {
const price = (Math.random() * 100).toFixed(2)
const payload = `data: ${price}\n\n`
for (const res of clients) res.write(payload)
}, 1000)
app.listen(3000, () => console.log(`SSE server on :3000`))
index.html
<!DOCTYPE html>
<meta charset=`utf-8`>
<h1>实时股票价格</h1>
<div id=`p`></div>
<script>
const es = new EventSource(`/prices`)
es.onmessage = e => document.getElementById(`p`).textContent = e.data
</script>
以上代码直接运行即可在浏览器看到价格逐秒跳动,无需第三方库,充分体现 text/event-stream
的轻量特性(Medium, DigitalOcean)。
性能与安全考量
-
连接数与资源占用
每个 SSE 连接占用一条 TCP 通道和服务器线程/协程,百万级并发需结合无阻塞 I/O、负载均衡乃至 HTTP/2 连接复用等手段扩展(Ably Realtime)。 -
CORS 与认证
EventSource 默认不携带凭证,若需跨域并附带 Cookie,应在构造函数传入{withCredentials:true}
并确保服务器设置Access-Control-Allow-Credentials: true
等头部(Cloudflare Docs)。 -
断线续传
利用id:
和Last-Event-ID
可实现幂等与增量同步,防止中间节点重连导致的数据缺口。 -
代理超时
若处在启用连接空闲检测的负载均衡或 CDN 之后,务必定期发送注释行保活,或调整代理的timeout
配置,避免连接被误杀(Stack Overflow)。 -
流量加密
SSE 可在 HTTPS 下透明运行,与 TLS 握手共用端口,保护数据机密性;浏览器安全模型与普通请求一致,无需额外策略。
典型使用场景
-
监控与告警:实时 KPI 面板、服务器健康度推送。
-
社交动态:点赞计数、微博热榜自动刷新。
-
协作工具:Markdown 预览同步渲染,在只需服务器单向广播的编辑器场景大放异彩。
-
日志与审计:DevOps 控制台输出 build 日志,用户端不必轮询即可实时获取。
-
物联网:传感器数据采集后台,只上传测量值,无须浏览器反向指令。
以上案例广泛存在于业界产品与开源组件,印证了 text/event-stream
的实践价值与生态成熟度(aklivity.io)。
结语
text/event-stream
用极低的心智成本与网络开销,给前端带来了稳定、简单且可断点续传的实时通讯手段。它不追求双向全能,而是在服务器只需说,浏览器安静听的模式下,把 HTTP 发挥到了极致。若你的业务场景以广播或轻量推送为主,不想在代理、防火墙、代码量上为 WebSocket 付出过多代价,那么 SSE 与 text/event-stream
依旧是 2025 年值得采用的现代方案。