SSE是客户端请求服务器后,服务器每隔一段时间向客户端发送数据(是单向的)。
详细可参照:http://www.ruanyifeng.com/blog/2017/05/server-sent_events.html
SSE 的本质
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
SSE 的使用
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SSE</title>
</head>
<body>
<h3>获取服务端更新数据</h3>
<div id="result"></div>
<script>
var source=new EventSource("/sse");
source.onmessage=function(event)
{
document.getElementById("result").innerHTML+=event.data + "<br>";
};
</script>
</body>
</html>
index.js
var app = require("express")();
app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
app.get("/sse", (req, res) => {
res.header({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
res.write("data: " + (new Date()) + "\n\n");
setInterval(() => {
res.write("data: " + (new Date()) + "\n\n");
}, 1000);
});
app.listen(3000, () => {
console.log("listening on *:3000");
});
执行 node index.js 启动服务,打开 http://localhost:3000/
会看到不断地刷新数据
那么服务器端如何关闭 sse 连接
首先我们通过 EventSource 自定义个关闭连接的 close 事件
source.addEventListener("close", function (event) {
source.close();
}, false);
然后通过 event 字段调用事件
setTimeout(() => {
clearInterval(interval);
res.write("event: close\n");
}, 4000);
代码如下:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SSE</title>
</head>
<body>
<h3>获取服务端更新数据</h3>
<div id="result"></div>
<script>
var source = new EventSource("/sse");
source.onmessage = function (event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
source.onerror = function (event) {
source.close();
};
source.addEventListener("close", function (event) {
source.close();
}, false);
</script>
</body>
</html>
index.js
var app = require("express")();
app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
app.get("/sse", (req, res) => {
res.header({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
const interval = setInterval(() => {
res.write("data: " + (new Date()) + "\n\n");
}, 1000);
setTimeout(() => {
clearInterval(interval);
res.write("event: close\n");
res.write("data: test close->" + (new Date()) + "\n\n");
}, 4000);
});
app.listen(3000, () => {
console.log("listening on *:3000");
});
只有三次,最后一次 test close 信息没有打出来,可见确实是关闭了
服务端返回数据需要特殊的格式,它分为四种消息类型:
event, data, id, retry
每个字段都有名称,紧接着有个”:“。当出现一个没有名称的字段而只有”:“时,这就会被服务端理解为”注释“,并不会被发送至浏览器端。
由于EventSource是基于HTTP连接之上的,因此在一段没有数据的时期会出现超时问题。服务器默认HTTP超时时间为2分钟,在node端可以通过response.connection.setTimeou(0)设置为默认的2min超时, 因此需要服务端做心跳保活,否则客户端在连接超时的情况下出现net::ERR_INCOMPLETE_CHUNKED_ENCODING错误。通过阅读相关规范,发现注释行可以用来防止连接超时,服务器可以定期发送一条消息注释行,以保持连接不断。
但是 EventSource 是不支持设置 request headers 的如果想设置有以下三种方法:
- 将 headers 放到 url 中
- 将 headers 放到 cookie 中,加上 { withCredentials: true } 参数,这样 EventSource 便会带上所有的 cookie
- 使用 EventSource polyfill