EventSource是基于http协议的推送技术,用于服务器主动向客户端发送数据,而不需要客服端主动请求
1. 优缺点
优点:
- 基于http1.1,目前浏览器都支持
- 自动断线重连,无需另写心跳检测
- 支持自定义消息类型
缺点:
- 单向通讯,客户端无法向服务端发送数据
- 仅支持纯文本,二进制数据需转码才能传送
相比于Websocket,EventSource更加轻量级,而且Websocket是独立协议,需要服务端支持 除了不能主动发送数据,可以说Websocket能做的,EventSource都能做
2. 应用场景
- ChatGPT
- 天气预报
- 股票外汇等价格牌
- 火车公交位置牌
3. 建立连接
EventSource使用简单,目前流行浏览器均支持
本文用node来写服务端的请求response
const express = require('express');
const cors = require('cors');
const app = express();
const port = 3001;
app.use(cors());
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
app.get('/get', (req, res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
res.flushHeaders();
let resp = () => {
const data = {
message: new Date().toLocaleTimeString()
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
const t = Math.random() * 1000 * 10
setTimeout(() => {
resp()
}, t)
};
resp()
});
代码比较简单,使用express框架,只需把Content-Type
属性设为text/event-stream
即可
随机每隔1-10秒输出当前时间来模拟间歇返回
前端页面的请求,只需要new EventSource(url)
对象,url参数是必须的
const source = new EventSource('http://localhost:3001/get');
支持第二个参数withCredentials
,主要用于跨域请求是否带上cookie
const source = new EventSource(url, { withCredentials: true })
完整代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="box"></div>
<script>
const dom = document.getElementById('box');
const source = new EventSource('http://localhost:3001/get');
source.onopen = event => {
console.log('open', source.readyState, event)
}
source.onmessage = event => {
console.log(source.readyState, event)
const data = JSON.parse(event.data)
const message = data.message
dom.innerHTML = `<p>${message}</p>${dom.innerHTML}`
};
</script>
</body>
</html>
运行后,服务端只要返回数据,则会在页面打印出返回的时间
4. 异常处理
EventSource具有断线重连特性,所以网络掉线或是服务端挂了,也会一直重试,通过error事件可以捕获到 type = 'error'
的异常
source.onerror = event => {
console.log('error', event)
}
可主动关闭连接,或是通过页面增加按钮来关闭,避免消耗资源(主要是服务端资源)
source.close()
同时可通过只读属性 source.readyState
来判断连接状态
0 = 连接中 1 = 已连接 2 = 已关闭
5. 自定义事件
在服务器发送的数据里,type即为事件名称,默认值为 message
type
对应服务端的 event
属性,自定义事件名时,需在服务端赋值,如下
data: 'test'\n event: up\n
表示 up 事件,此时浏览器收到数据流如下
前端使用时,需绑定 up 事件
source.addEventListener('up', event => {
console.log('up', event)
})
6. 重连间隔
服务端发给客户端的信息,采用 \n\n
作为每个信息包的分隔,除了上面用到的data
和event
,还支持 id
、retry
等字段
id
为每条消息的标识id
retry
为无通讯时客户端主动连接的间隔事件,单位为毫秒
即服务端发送的文本,可以是这样
id: 2221\n data: '这是一段很长的文本'\n retry: 10000\n\n
如果是很长的文本,甚至可以把 data
拆成几行
id: 2221\n data: '这是一段很长的文本'\n data: '文本的第二行'\n data: '文本的第三行'\n retry: 10000\n\n
总之只有识别到 \n\n
才算是一次数据的结束
一般来说,服务端主动发送的间隔,要小于重连间隔,如果超过重连时间(在chrome上测试,默认间隔为5秒),则浏览器会自动请求服务器,并触发异常事件
7. 相关资料
阮一峰《Server-Sent Events 教程》 https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html
江辰《EventSource 引发的一系列事件》 https://juejin.cn/post/7226959605100134455