SSE
SSE
基本概念
Server-Sent Events(SSE)是一种允许服务器向客户端实时推送更新的 Web 技术。与传统的请求 - 响应模式不同,SSE 建立了一个单向的通信通道,服务器可以主动向客户端发送数据,而客户端只能被动接收。这种特性使得 SSE 非常适合用于实时数据更新的场景,如实时新闻推送、股票行情更新等。
本质
参考文献:阮一峰
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE,其他浏览器都支持。
与WebSocket 区别
特性 | SSE (Server-Sent Events) | WebSocket |
---|---|---|
通信方向 | 单工(服务器到客户端) | 全双工 |
协议 | 基于HTTP | 基于 TCP |
自动重连 | 内置断线重连和消息追踪的功能 | 不在协议范围内,需手动实现 |
连接数 | HTTP/1.1 限制为每个域名 6 个 ;HTTP/2 可协商(默认 100) | 主要取决于服务器配置和资源,而非浏览器固定限制 |
数据格式 | 仅文本(二进制数据需要编码后传送) | 支持文本和二进制 |
量级 | 轻量级,使用简单 | 相对复杂 |
状态 | 无状态( 基于HTTP 协议) | 有状态 |
使用场景 | 单向数据流、通知、实时更新 | 需要双向通信的应用、聊天、游戏 |
自定义事件 | 支持 自定义事件类型 | 不支持 |
浏览器支持 | 较广泛,IE不支持 | 全面支持 |
EventSource 对象
参考文献:mdn
EventSource
接口是 web 内容与服务器发送事件通信的接口。一个 EventSource
实例会对 HTTP 服务器开启一个持久化的连接,以 text/event-stream
格式发送事件,此连接会一直保持开启直到通过调用 EventSource.close()
关闭。
兼容性
SSE 的客户端 API 部署在EventSource
对象上。下面的代码可以检测浏览器是否支持 SSE。其中:不兼容IE。
1 | if ('EventSource' in window) { |
Server-Sent Events (SSE)只能使用 GET 请求进行连接。
构造函数
使用 SSE 时,浏览器首先生成一个EventSource
实例,向服务器发起连接。
1 | const eventSource = new EventSource(url, options); |
上面的url
可以与当前网址同域,也可以跨域。跨域时,可以指定第二个参数,打开withCredentials
属性,表示是否一起发送 Cookie。
1 | const eventSource = new EventSource(url, { withCredentials: true }); |
实例属性
EventSource.readyState
:表明连接的当前状态。该属性只读,可以取以下值。0:相当于常量
EventSource.CONNECTING
,表示连接还未建立,或者断线正在重连。1:相当于常量
EventSource.OPEN
,表示连接已经建立,可以接受数据。2:相当于常量
EventSource.CLOSED
,表示连接已断,且不会重连。
1
2
3
4
5
6
7if (eventSource.readyState === EventSource.CONNECTING) {
console.log('正在连接服务器...');
} else if (eventSource.readyState === EventSource.OPEN) {
console.log('已经连接上服务器!');
} else if (eventSource.readyState === EventSource.CLOSED) {
console.log('连接已经关闭。');
}EventSource.url
:表示事件源的URL字符串,该属性只读。EventSource.withCredentials
:一个布尔值,指示EventSource
对象是否使用 CORS 凭据设置进行实例化(true
),或未使用 CORS 凭据设置进行实例化(false
,默认值)。
实例方法
EventSource
的方法**close()
**用于关闭当前的连接,如果调用了这个方法,底部将EventSource.readyState
这个属性值设置为2(关闭)
1 | eventSource.close(); |
事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | eventSource.onopen | 连接建立时触发 |
message | eventSource.onmessage | 客户端接收服务端数据时触发 |
error | eventSource.onerror | 通信发生错误时触发(比如连接中断) |
数据格式
参考文献:前端也能这么丝滑!Node + Vue3 实现 SSE 流式文本输出
服务器向浏览器发送的 SSE 数据,必须是 UTF-8 编码的文本,具有如下的 HTTP 头信息。其中Content-Type必须指定 MIME 类型为text/event-stream
1 | Content-Type: text/event-stream |
后端推送的数据格式有点讲究,必须长这样:
1 | data: 你要发的内容\n\n |
每条消息都要以两个换行结尾(\n\n
),否则前端收不到。比如express后端这样写:
1 | res.write("data:hello world\n\n"); // 这里必须使用res.write() 而不能使用其他;(因为其他方法会自动关闭连接) |
前端就能在 event.data
里拿到 hello world
。
除了 data:
,其实还可以有这些字段:
id:
消息编号,前端可以拿来做断点续传event:
自定义事件类型,默认是message
事件,前端可以用addEventListener
监听该事件retry:
服务器可以用retry
字段,指定浏览器重新发起连接的时间间隔(毫秒);两种情况会导致浏览器重新发起连接:一种是时间间隔到期,二是由于网络错误等原因,导致连接出错。
具体代码
完整代码地址:https://github.com/fsllala/study/tree/main/sse_study
后端
1 | const express = require('express'); |
前端
1 | const eventSource = new EventSource('http://localhost:3000/sse'); |
测试效果如下:
自定义事件类型
我们可以通过测试效果看到,ID为空,类型为message,这里我们可以进行修改:
1 | setInterval(() => { |
1 | eventSource.addEventListener('changedMessage', (event) => { // 前端监听的事件要和后端类型保持一致 |
打字机效果
1 | const express = require('express'); |
1 | const eventSource = new EventSource('http://localhost:3000/sse'); |
fetch
参考文献:你知道AI如何通过SSE流式渲染到页面的吗(附带完整案例)
Server-Sent Events (SSE)只能使用 GET 请求进行连接。如果接口是POST请求,则不能通过SSE实现,需要借助fetch。(ajax不能读取流数据)
使用场景:
携带请求头信息:原生 EventSource 不支持在构造函数中添加自定义请求头
参数传递有限:只能通过 URL 查询参数传递数据到后端。例如:
1
const eventSource = new EventSource('http://localhost:3000/sse?token=abc123&userId=123');
后端
1 | const express = require('express'); |
前端
前端需要借助fetch请求POST的SSE。
1 | async function startSSE() { |
这里虽然SSE一直在推送数据,但是前端仅仅收到一次;因为仅仅await reader?.read()
读取了一次,并且通过 decoder.decode(read?.value)
解析了一次;如果一直读取解析需要写一个while
循环;
1 | async function startSSE() { |
获取具体的data数据
1 | while (true) { |
但是使用fetch是没有自动重连的;虽然可以自己实现,但是还是太繁琐了,这里可以借助第三方库。
fetch-event-source
- 下载地址:@microsoft/fetch-event-source
- 前端安装依赖:
npm install @microsoft/fetch-event-source
1 | // 前端代码 (后端不做修改) |
效果如下动图所示:可以看到可以像EventSource 一样接收数据,并可以自动重连。
微信小程序
微信小程序不支持标准的 EventSource 和 fetch API。对于 SSE (Server-Sent Events),需要通过wx.request() + enableChunked: true + onChunkReceived 回调来模拟。
参考文献:
具体实现
官网wx.request 在2.20.2基础库后加上了enableChunked(启用分块)属性。
- 属性:
enableChunked
启用分块。 - 方法:
RequestTask.onChunkReceived(function listener)
监听 Transfer-Encoding Chunk Received 事件。当接收到新的chunk时触发。 - 方法:
RequestTask.offChunkReceived(function listener)
移除 Transfer-Encoding Chunk Received 事件的监听函数。
1 | const requestTask = wx.request({ |
现象:微信开发者工具的Network
看不到数据的推送,但是onChunkReceived
可以监听并打印出数据的推送;
解析ArrayBuffer
需要下载text-encoding
插件。text-encoding
是一个用于处理文本编码和解码的库,特别适合在微信小程序中处理 ArrayBuffer 数据。这里就涉及到了小程序下载npm
:微信官方文档-npm 支持
- 初始化 package.json:在根目录,
cmd
之后npm init -y
- 下载依赖:
npm install text-encoding --save
- 通过微信开发者工具构建
npm
:在微信开发者工具中点击”工具” -> “构建 npm”,将 npm 包构建到小程序中。
- 引入依赖包,并使用;
1 | // xxx.js |
在 SSE 场景中的完整示例
1 | const TextDecoder = require('text-encoding').TextDecoder; |