EventSource
当不使用 HTTP/2 时, 服务器发送事件(SSE)受到打开连接数(6)的限制, 这个限制是针对浏览器的. 每个浏览器的所有标签页中对相同域名的连接数最多支持 6 个(chrome 超过 6 个自动断开连接, firefox 超过 9 个自动断开连接)
当使用 HTTP/2 时, 最大并发 HTTP 流的数量是由服务器和客户端协商的(默认为 100)
Server-Sent Events 服务器发送事件
Web 内容与服务器发送事件通信的接口, 通信方向是单向的, 数据消息只能从服务器发送到客户端. 如果接收消息中有一个 event 字段, 触发的事件与 event 字段的值相同, 如果不存在 event 字段, 则将触发通用的 message 事件
url, 表示远程资源的位置
configuration, 可选, 一个对象
- withCredentials, 标识 CORS 是否包含凭据, 默认为 false
不支持恢复连接, 连接中断后无法从 端点 继续, 只能重新开始新的连接, 之前的上下文可能会丢失
要求服务器保持高可用的长连接, 服务器必须一直保持一个稳定、不中断的 SSE 长连接, 否则通信就中断
服务器只能通过 SSE 发送消息, 服务器无法在已有的请求之外, 主动的发消息给客户端, 除了通过专门的 /sse 通道
消息格式
数据以 文本流(text/event-stream) 的形式从服务器发送到客户端, 每一行数据都必须以 换行符(\n) 结束, 每条消息以两个 换行符(\n\n) 结束
- event 自定义事件类型(可选), 不使用默认触发 message
- id 消息 ID, 用于断线重连定位
- retry 重连间隔(毫秒)
- data 数据内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const sse = new EventSource(url, configuration);
sse.onmessage = function (e) { console.log(e); };
sse.addEventListener('notice', (e) => { console.log(evt); });
|
实例属性
- readyState, 标识连接状态的数字
- CONNECTING(0)
- OPEN(1)
- CLOSED(2)
- url, 表示远程资源的位置的字符串
- withCredentials, 标识是否使用跨域资源共享(CORS)凭据来实例化
实例方法
- close(), 关闭链接, 并将 readyState 属性设置为 CLOSED
事件
- error, 连接失败时触发
- message, 接收到数据时触发
- open, 连接打开时触发
连接服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <h1>SSE(event-source) Demo</h1> <p><button id="event-source">Start SSE</button> <button id="close">Close</button></p> <ul id="list"></ul> <script> let sse = null; const list = document.getElementById('list'); document.getElementById('event-source').addEventListener('click', function () { sse = new EventSource('/evt-source', { withCredentials: true }); const liMessageColor = 'rgb(' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ')' const liNoticeColor = 'rgb(' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ')'
sse.addEventListener('notice', function (e) { console.log('notice', e) const data = JSON.parse(e.data) const li = document.createElement('li') li.style.color = liNoticeColor li.innerHTML = 'event: ' + e.type + ' count:' + data.count list.appendChild(li) })
sse.addEventListener('message', function (e) { console.log('message', e) const data = JSON.parse(e.data) const li = document.createElement('li') li.style.color = liMessageColor li.innerHTML = 'event: ' + e.type + ' count:' + data.count list.appendChild(li) })
sse.addEventListener('open', function (e) { console.log('open', e) const li = document.createElement('li') li.innerHTML = 'event: ' + e.type; list.appendChild(li) })
sse.addEventListener('connected', function (e) { console.log('connected', e) const li = document.createElement('li') li.innerHTML = 'event: ' + e.type; list.appendChild(li) })
sse.addEventListener('error', function (e) { console.log('error', e) const li = document.createElement('li') li.innerHTML = 'event: ' + e.type; list.appendChild(li) }) });
document.getElementById('close').onclick = function () { sse?.close(); }; </script>
|
服务器推送数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import express from 'express';
const app = express() let count = 1
app.use(express.static('./'))
app.get('/evt-source', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive');
const sendEvent = (evtName, data) => {
res.write(`event: ${evtName}\nid: ${Date.now()}\nretry: 500\ndata: ${JSON.stringify(data)}\n\n`); };
sendEvent('connected', { message: 'Connection established' });
const timer = setInterval(() => { sendEvent(Math.random() > 0.5 ? 'notice' : 'message', { message: `Message at ${new Date().toISOString()}`, count: count }); count++; }, 1000);
req.on('close', () => { clearInterval(timer); res.end() }); })
app.get('/eventSource.html', (req, res) => { res.sendFile('./eventSource.html') })
app.listen(8080, () => { console.log('app listen localhost:8080') })
|