Sever-send Events

Server-send events

SSE

服务器向浏览器推送信息,除了 WebSocket,还有一种方法:Server-Sent Events(简称 SSE)

说明

严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。

也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。

SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持

与 WebSocket 对比

两者都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息

  • 不同点
    • SSE 使用 HTTP 协议,相对轻量,WebSocket 使用 WS 协议,相对复杂
    • SSE 是单向通道,WebSocket 是全双工通道
    • SSE 支持自动断线重连,WebSocket 不支持
    • SSE 一般只用来传输文本,二进制数据需要编码后传送,WebSocket 默认支持二进制数据传输
    • SSE 支持自定义的接受消息事件类型,WebSocket 只支持 message 事件类型

EventSource

通过 HTTP 连接服务器,获取服务器推送的信息

属性

  • readyState

    • CONNECTING

      值为 0,表示连接尚未建立或已关闭但尝试重新连接中

    • OPEN

      值为 1,表示已经建立连接

    • CLOSED

      值为 2,表示连接已关闭,可能调用了 close 方法或者发生语法错误

  • url

    连接请求地址

  • withCredentials

    初始化时是否设置 withCredentials 属性,这个属性指明是否需要跨域

API

  • open()

  • close()

  • error()

  • message()

  • 其他自定义事件

    默认情况下,浏览器接收到服务器发送的数据时触发的是 message 事件,但也支持自定义事件

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
const eventSource = new EventSource('http://127.0.0.1:1337');

eventSource.addEventListener('open', _ => {
console.info('建立连接');
}, false);

eventSource.addEventListener('close', _ => {
console.info('连接关闭');
}, false);

eventSource.addEventListener('error', error => {
console.info('连接异常', error);
}, false);

eventSource.addEventListener('message', event => {
console.dir(event);
document.getElementById('message').innerHTML = event.data;
}, false);

eventSource.addEventListener('update', event => {
const data = JSON.parse(event.data);

console.dir(event);
document.getElementById('result').innerHTML = data.count;
}, false);

eventSource.close();

服务器实现

  • HTTP 响应头

    服务器向浏览器发送的数据必须是 UTF-8 编码的文本,HTTP Response 头需包含以下信息

    1
    2
    3
    4
    "Content-Type":"text/event-stream",
    "Cache-Control":"no-cache",
    "Connection":"keep-alive",
    "Access-Control-Allow-Origin": '*',
  • message 格式

    服务器发送的信息有若干个 message 组成,每个 message 之间用 \n\n 间隔

    field 字段可以取四个值,data、id、retry、event;也可以为空

    1
    [field]: value\n
    • 为空时,表示注释,服务器每隔一段时间就会向浏览器发送一个注释,保持连接不中断

      1
      res.write(": comment");
    • data

      如果数据很长,可以分成多行,最后一行用 \n\n 结尾,前面行都用 \n 结尾

      1
      2
      data: begin message\n
      data: continue message\n\n
    • id

      表示数据的编号,浏览器用lastEventId属性读取这个值。一旦连接断线,浏览器会发送一个 HTTP 头,里面包含一个特殊的Last-Event-ID头信息,将这个值发送回来,用来帮助服务器端重建连接

      1
      2
      id: 123456\n
      data: message\n\n
    • event

      表示自定义事件的类型,默认为 message 事件

      1
      2
      3
      4
      event: update\n
      data: message\n\n

      data: message\n\n
    • retry

      表示浏览器重新发起连接的时间间隔,一般只有时间间隔到期或发生网络错误才会重新发起连接

      1
      retry: 10000\n

Demo

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
// node
const http = require('http');
const PORT = 8201;

const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

const generateMessage = (eventType = 'message') => {
return `event: ${eventType}\ndata:${random(0, 100)}\n\n\n`;
}

const generate = (request, response) => {
setInterval(() => {
response.write(generateMessage('update'));
}, 3000);
};

http.createServer((request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
response.setHeader('Access-Control-Allow-Headers', '*');
response.setHeader('connection', 'keep-alive');
response.setHeader('cache-control', 'no-cache');
response.setHeader('content-type', 'text/event-stream');

if (request.method === 'OPTIONS') {
response.writeHead(200);
response.end();
return;
}

switch (request.url) {
case '/generate':
generate(request, response);
break;
default:
response.writeHead(404);
response.end();
}
}).listen(PORT);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const eventSource = new EventSource('http://localhost:8201/generate');

eventSource.addEventListener('open', _ => {
console.info('建立连接');
}, false);

eventSource.addEventListener('close', _ => {
console.info('连接关闭');
}, false);

eventSource.addEventListener('error', error => {
console.info('连接异常', error);
}, false);

eventSource.addEventListener('message', event => {
console.info(event.data);
}, false);

兼容性方案

IE 及 Android 2.1~4.3 不支持 EventSource

这里列出了多个 Polyfills,但从提交记录、测试等考量后,https://github.com/amvtek/EventSource 更适合为兼容方案,支持 IE8+ 及 Android Browser 2.1+

其它

参考