#详解 Server Sent Events
本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com
#Server Sent Events 的使用场景
通常情况下,客户端(以 Web App 为例)想要获取后端的数据就需要先发一个 HTTP 请求到后端,后端以 HTTP 响应的方式返回数据 。
这种通讯形式我们已经非常熟悉了,主要特点
- 每次通讯都需要由 Web App 发起
- Http request 和 Http response 都是一对一 出现
假如现在需要经常获取后端的通知( 比如 新闻推送,消息推送),如果用这种方式实现只能用轮询做法,即
这种方案的缺点非常明显
- 大量无用的请求
- 设定轮询间隔是件挺尴尬的事情 —— 太短导致更多无用请求,浪费资源;太长导致无法及时收到后端的通知
有读者可能知道 WebSocket,但是 WebSocket 更适合双方需要相互通信的场景,比如聊天室 APP。而且 WebSocket 方案比较重,需要改动更多代码、使用不同的协议(非 HTTP )。
仅后端需要频繁向客户端推送通知 / 数据的场景,使用 WebSocket 如同用牛刀杀鸡。
这种场景下,Server Sent Events 就是非常合适的方案 —— 轻量 + 基于 HTTP 协议,后端可以在任何时刻向我们的 Web 页面推送数据和信息。
如下图所示, SSE 是由 客户端向后端发起一个请求,建立起长连接 ( keep-alive connection)
之后,后端可以随时往客户端推送数据了。

#SSE 协议和数据格式
前面提到了, SSE 其实还是基于 HTTP 协议的,因此我们寻常使用的 HTTP Client Library 和 HTTP Server Library 仍然适用。唯一需要注意的是 HTTP response 里 header Content-Type 的值是 text/event-stream。
数据格式方面, SSE 使用的是 UTF8 编码的文本格式。
使用两个换行符来分隔前后的消息,每条消息支持 4 种属性,属性名和属性值之间用冒号区分,新起一行(即使用一个换行符)创建下一个属性。
举个例子:
id: D420B6E8-2F51-4235-B778-5C1C494681E8
event: city-notification
retry: 3000
data: Plainville
id: 3AC67AA0-2852-4D30-9103-23CEF4B43D6D
event: city-notification
retry: 3000
data: Bellevue
上面有两条消息(两个 server sent event),两条消息之间有一个空行 —— 即'Plainville' 与下一个'id' 之间有 2 个换行符。
每条消息都有 4 个属性:
- id - 当前消息的 id
- event - 当前消息的类型,根据业务需要自行定义。比如例子里是 city-notification ,如果是天气推送,这个值可以是 weather;新闻推送,这个值可以是 news ...
- data - 这条消息的内容,只能为文本类型 —— 可以像例子里直接一个字符串,也可以是 JSON 字符串或者自定义的字符串格式
- retry - 值必须是数字(非数字自动忽略), 不同于其他的属性,这个属性跟当前消息无关,而是跟这次 SSE 连接相关 —— 如果连接中断,客户端应该间隔多少毫秒再尝试重新连接。
只有上述四种属性是合法,其他属性都会被忽略。
几个关键点:
- HTTP response 的 Content-Type 的值是 text/event-stream —— text 表明了通讯数据是明文,event-stream 表明了是事件流的形式
- 数据格式和属性已经在前面解释过了
- 由于是数据流的形式 —— 因此每发一条消息,都会调用一次 FlushAsync 方法,以确保响应数据被发送出去了 (有的 HTTP server library 的底层代码实现里会把数据放在队列里直到某些条件满足了才一起发送)
值得注意的是没有 close 事件 —— SSE 中后端是无法主动关闭连接的,必须由客户端关闭。
对此,一种常见的方案就是模拟 close 事件
回看上面后端的代码,我们自定义了一种消息(事件)
event: close
retry: 3
data: bye
前端只需要监听这类事件,主动关闭连接
(这边的 close 事件不在规范中,因此我们完全可以自定义命名,比如 terminate, stop, end 等等)
当然更多时候,SSE 的连接就是不需要关闭的 —— 时刻等待着推送新通知。
#进阶之其他痛点和解决方案
#UTF8 编码的局限性
SSE 的内容目前规范里只支持 UTF8 编码,如果推送的内容包含其他编码或者图片、文件等怎么办呢?
解决方案:
其实不难,只需要借助额外的后端 API 就行了。
即 SSE 消息中只包含简单的元数据(例如,图片 id,文件 id/url,复杂消息 id 等),前端发起另外的请求去获取这些复杂的内容。
#Probot 和 smee-client 如何使用 SSE
学完上面的 SSE 知识,再去看 probot 和 smee-client 的源码就很轻松了 ——
- https://github.com/probot/smee-client/blob/master/index.ts
- https://github.com/probot/probot/blob/master/src/helpers/webhook-proxy.ts
基本原理就是
- Probot 本地起了一个 http server
- Probot 利用 EventSource 监听 smee.io SSE endpoint 即 WEBHOOK_PROXY_URL
- 接到 SSE 消息通知就发送 http request 给自己的 http server API —— https://github.com/probot/smee-client/blob/587e09c6e509d995e210aa92771525639f3d2441/index.ts#L46
由于难度不大,有兴趣的小伙伴自行阅读源码理解。
参考链接:
Using server-sent events - Web APIs | MDN
DEMO 源码: https://github.com/freewheel70/SSEDemoApp
#评论
#评论 1 · 2023-02-28T06:17:34.229000Z
Content-Type 的值是 text/event-stream。