一、Server-Sent Events规范描述了一个内建的类EventSource,EventSource对象自动建立一个持久的连接,并允许服务器通过这个连接发送消息。
1、与WebSocket类似,其连接是持久的。
2、与WebSocket相比,EventSource是与服务器通信的一种不那么强大的方式。
二、EventSource在所有现代浏览器(除了 IE)中都得到了支持。
优缺点
一、优点 / 我们为什么要使用它?
- 主要原因:简单。在很多应用中,WebSocket有点大材小用。
- 低延迟:消息即时到达,不发无用请求
- 它是一个普通的旧的 HTTP,不是一个新协议。
二、缺点
- 服务器维护一个长连接会增加开销
适用场景
一、我们需要从服务器接收一个数据流:可能是聊天消息或者市场价格等。这正是EventSource所擅长的。它还支持自动重新连接,而在WebSocket中这个功能需要我们手动实现。
获取消息
一、要开始接收消息,我们只需要创建new EventSource(url)即可。
1、浏览器将会连接到url并保持连接打开,等待事件。
2、服务器响应状态码应该为 200,header 为Content-Type: text/event-stream,然后保持此连接并以一种特殊的格式写入消息,就像这样:
data: Message 1
data: Message 2
data: Message 3
data: of two lines
- data:后为消息文本,冒号后面的空格是可选的。
- 消息以双换行符\n\n分隔。
- 要发送一个换行\n,我们可以在要换行的位置立即再发送一个data:(上面的第三条消息)。
二、在实际开发中,复杂的消息通常是用 JSON 编码后发送。换行符在其中编码为\n,因此不需要多行data:消息。
| 【示例】```javascript data: {“user”:”John”,”message”:”First line\n Second line”}
1、因此,我们可以假设一个data:只保存了一条消息。<br />2、对于每个这样的消息,都会生成message事件:```javascript
let eventSource = new EventSource("/events/subscribe");
eventSource.onmessage = function(event) {
console.log("New message", event.data);
// 对于上面的数据流将打印三次
};
// 或 eventSource.addEventListener('message', ...)
| | —- |
三、服务器可以在event:中设置自定义事件名称。应该使用addEventListener来处理此类事件,而不是使用on
服务器响应格式
一、服务器发送由\n\n分隔的消息。
二、一条消息可能有以下字段:
- data:—— 消息体(body),一系列多个data被解释为单个消息,各个部分之间由\n分隔。
- id:—— 更新lastEventId,重连时以Last-Event-ID发送此 id。
- retry:—— 建议重连的延迟,以 ms 为单位。无法通过 JavaScript 进行设置。
- event:—— 事件名,必须在data:之前。
二、一条消息可以按任何顺序包含一个或多个字段,但是id:通常排在最后
跨源请求
一、EventSource支持跨源请求,就像fetch任何其他网络方法。我们可以使用任何 URL:
let source = new EventSource("https://another-site.com/events");
二、远程服务器将会获取到Originheader,并且必须以Access-Control-Allow-Origin响应来处理。
要传递凭证(credentials),我们应该设置附加选项withCredentials,就像这样:
let source = new EventSource("https://another-site.com/events", {
withCredentials: true, // 允许发送跨源凭证
});
重新连接
一、创建之后,new EventSource连接到服务器,如果连接断开 —— 则重新连接。
1、这非常方便,我们不用去关心重新连接的事情。
二、每次重新连接之间有一点小的延迟,默认为几秒钟。
1、服务器可以使用retry:来设置需要的延迟响应时间(以毫秒为单位)。
retry: 15000
data: Hello, I set the reconnection delay to 15 seconds
2、retry: 既可以与某些数据一起出现,也可以作为独立的消息出现。
三、在重新连接之前,浏览器需要等待那么多毫秒。甚至更长,例如,如果浏览器知道(从操作系统)此时没有网络连接,它会等到连接出现,然后重试。
1、如果服务器想要浏览器停止重新连接,那么它应该使用 HTTP 状态码 204 进行响应。
2、如果浏览器想要关闭连接,则应该调用eventSource.close():
let eventSource = new EventSource(...);
eventSource.close();
四、如果响应具有不正确的Content-Type或者其 HTTP 状态码不是 301,307,200 和 204,则不会进行重新连接。在这种情况下,将会发出”error”事件,并且浏览器不会重新连接。
五、当连接最终被关闭时,就无法“重新打开”它。如果我们想要再次连接,只需要创建一个新的EventSource。
EventSource对象的属性
连接状态:readyState
一、EventSource对象有readyState属性,该属性具有下列值之一:
EventSource.CONNECTING = 0; // 连接中或者重连中
EventSource.OPEN = 1; // 已连接
EventSource.CLOSED = 2; // 连接已关闭
二、对象创建完成或者连接断开后,它始终是EventSource.CONNECTING(等于0)。
三、我们可以查询该属性以了解EventSource的状态。
消息 id:lastEventId
一、当一个连接由于网络问题而中断时,客户端和服务器都无法确定哪些消息已经收到哪些没有收到。
二、为了正确地恢复连接,每条消息都应该有一个id字段,就像这样:
data: Message 1
id: 1
data: Message 2
id: 2
data: Message 3
data: of two lines
id: 3
三、当收到具有id的消息时,浏览器会:
1、将属性eventSource.lastEventId设置为其值。
2、重新连接后,发送带有id的 header Last-Event-ID,以便服务器可以重新发送后面的消息。
四、id被服务器附加到data消息后,以确保在收到消息后lastEventId会被更新。
EventSource对象的方法
- close()
EventSource对象的事件
Event 类型
一、默认情况下EventSource对象生成三个事件:
- message—— 收到消息,可以用event.data访问。
- open—— 连接已打开。
- error—— 无法建立连接,例如,服务器返回 HTTP 500 状态码。
二、服务器可以在事件开始时使用event: …指定另一种类型事件。
event: join
data: Bob
data: Hello
event: leave
data: Bob
三、要处理自定义事件,我们必须使用addEventListener而非onmessage:
eventSource.addEventListener('join', event => {
alert(`Joined ${event.data}`);
});
eventSource.addEventListener('message', event => {
alert(`Said: ${event.data}`);
});
eventSource.addEventListener('leave', event => {
alert(`Left ${event.data}`);
});
完整示例
| 一、效果
1、服务器依次发送1,2,3,最后发送bye并断开连接。
2、然后浏览器会自动重新连接。
二、示例代码
1、index.html```javascript
<!DOCTYPE html>
Press the “Start” to begin.
“Stop” to finish
2、server.js```javascript
let http = require('http');
let url = require('url');
let querystring = require('querystring');
function onDigits(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache'
});
let i = 0;
let timer = setInterval(write, 1000);
write();
function write() {
i++;
if (i == 4) {
res.write('event: bye\ndata: bye-bye\n\n');
clearInterval(timer);
res.end();
return;
}
res.write('data: ' + i + '\n\n');
}
}
function accept(req, res) {
if (req.url == '/digits') {
onDigits(req, res);
return;
}
fileServer.serve(req, res);
}
if (!module.parent) {
http.createServer(accept).listen(8080);
} else {
exports.accept = accept
}
| | —- |