速成 Server-Sent Events

最后更新于

SSE 是一种基于 HTTP GET 实现的从服务端往浏览器端 推送 消息的协议,比 WebSocket 好写(功能也很局限),当需要快速实现一个简单的消息推流时可以采用他。webpack-dev-server 也使用了 SSE 来实现通知浏览器刷新。

接下来我按时间顺序,分别说明前后端要干什么事:

1. 前端使用 EventSource 发起一个 GET 请求

const source = new EventSource(`/__server`);

2. 后端开始响应,并且保持连接

后端会接收到一个 GET /__server 的请求,此时我们写上固定的返回头:

res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
});
// 保存当前连接 res 到某个全局容器
let clients: Set<ServerResponse>
clients.add(res)

就算是建立好连接了。接着,后端可能会在什么异步任务里对这些前端发送消息:

for (const res of clients) {
    res.write("data: hello world\n\n");
}

消息以 \n\n 分开,具体能发的消息格式和前端拿到时候的样子见下文。

3. 前端监听连接消息、主动断开连接

// 已建立连接
source.onopen = () => console.log('connected')
// 已丢失连接
source.onerror = () => console.log('error')
// 断开连接
source.close()
// 接收消息
source.addEventListener('message', (event) => {
    console.log(event.data);
})

4. 消息格式

消息必须使用 UTF-8,另外,允许在开头存在 BOM (\xFEFF)。

: 注释行,一般只用来保持连接

data: 普通消息

: ↑ 前端使用 addEventListener 'message' 来获取消息,event.data 是 "普通消息"

data: 两行的
data: 消息

: ↑ 此时 event.data 是 "两行的\n消息"

event: debug
data: 特定消息

: ↑ 前端使用 addEventListener 'debug' 来获取消息,event.data 是 "特定消息"

id: 1145141919810

: ↑ 这会导致接下来的消息里,event.lastEventId 是 '1145141919810',原来是 ''

retry: 5000

: ↑ 设置前端自动超时重试时间为 5 秒

特殊情况,这行没有冒号

: ↑ 相当于在最后添加一个冒号再处理,由于不是已知前缀之一,会被忽略

data

: ↑ 相当于 "data:",前端会得到一个消息,其 event.data 是 ''

data:  123

: ↑ 其 event.data 是 ' 123',冒号右侧第一个空格会被忽略,没有也没关系