随着ChatGpt的流行,大家了解了逐字显示的效果原来如此简单,而实现这个效果的关键技术就是流式的接口请求技术SSE,服务器发送事件(Server-Sent Events),这是一种单向通信协议,通常用于需要实时数据更新的应用场景,例如新闻更新、社交媒体通知、股票行情等。
一、Javascript里的SSE
javascript里,它的名字叫EventSource,想不到这个小哥如今已经十岁了。
EventSource 接口是 HTML5 规范的一部分,它允许 Web 页面通过标准 HTTP 连接接收服务器推送的数据。EventSource 接口在 2009 年被引入 HTML5 规范,并在随后的几年中得到了浏览器的支持。
浏览器支持时间线:
-
Firefox:在 Firefox 4.0 中首次支持
EventSource,该版本于 2011 年 3 月发布。 -
Chrome:在 Chrome 9.0 中添加了对
EventSource的支持,该版本于 2010 年 11 月发布。 -
Safari:在 Safari 6.0 中支持
EventSource,该版本于 2012 年 7 月发布。 -
Internet Explorer:直到 Internet Explorer 10.0,微软才添加了对
EventSource的支持,该版本于 2012 年 10 月发布。
所以在兼容性上,该API应该没什么太大问题了。
EventSource 的特点
-
单向通信:服务器向客户端推送消息,客户端不能向服务器发送消息。
-
文本流:数据以文本形式发送,通常是 UTF-8 编码。
-
自动重连:如果连接中断,浏览器会自动尝试重新连接。
-
事件命名:可以为消息指定不同的事件类型,客户端可以根据事件类型选择性地处理消息。
到这里,大家应该也体会到了,SSE简直就是为了GPT而生的,时隔10年,真是天生我材必有用,流式调用还得是我。下面来看看它的一些基础用法。
二、EventSource的使用
一起来揭开 EventSource 的神秘面纱,看看它是如何工作的。
首先看看浏览器端是如何发生和接受数据的:
// 创建一个新的 EventSource 对象,指向服务器端的 SSE 接口
const eventSource = new EventSource('server-sent-events-url');
// 监听默认的消息事件
eventSource.onmessage = function(event) {
console.log('收到消息:', event.data);
};
// 监听自定义事件
eventSource.addEventListener('自定义事件', function(event) {
console.log('收到自定义事件消息:', event.data);
});
// 监听错误事件
eventSource.onerror = function(event) {
console.error('EventSource 发生错误:', event);
};
很友好的一个API,比XMLHttpRequest还友好。构造函数接受两个参数,url和configuration对象,configuration对象只有一个选项:withCredentials,默认为 false,指示 CORS 是否应包含凭据 ( credentials )。
一个 EventSource 实例会对 HTTP 服务器开启一个持久化的连接,以 text/event-stream 格式发送事件,此连接会一直保持开启直到通过调用 EventSource.close() 关闭。
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
来自服务端传入的消息会以事件的形式分发至你代码中。如果接收消息中有一个 event 字段,触发的事件与 event 字段的值相同。如果不存在 event 字段,则将触发通用的 message 事件。
下面来看看服务器是如何发送数据的:
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// 发送自定义事件
setInterval(() => {
res.write(`event: sourceEvent\n`);
res.write(`data: 这是一个自定义事件的消息 - ${new Date().toLocaleTimeString()}\n\n`);
}, 3000);
// 保持连接,避免断开
setInterval(() => {
res.write(': keep-alive\n\n');
}, 15000);
} else {
res.writeHead(404);
res.end();
}
}).listen(8080, () => {
console.log('服务器在 http://localhost:8080 运行...');
});
如果不需要自定义事件,可以直接写数据:
setInterval(() => {
res.write(`data: ${new Date().toLocaleTimeString()}\n\n`);
}, 1000);
其中data的格式如下:
data: 这是一个消息
event: 自定义事件
data: 这是带有事件类型的消息
retry: 5000 // 重新连接间隔时间(毫秒)
这里有几点需要注意的:
1、冒号开头表示注释行
在 EventSource 中,发送数据的行以冒号开头表示注释行。这些行是发送给客户端的非事件数据,浏览器会忽略它们,但它们有助于保持连接活跃。
2、数据的开头必须是特定的标识
EventSource 发送数据时需要以特定的关键字开头,如 data、event、id、retry 和 :(注释行)
-
data:指定事件的数据内容。可以跨多行。
-
event:指定事件的类型,用于自定义事件。
-
id:指定事件的唯一标识符。客户端会保存这个 ID,并在重新连接时发送
Last-Event-ID头,服务器可以利用这个 ID 发送从上次连接后更新的数据。 -
retry:指定客户端在连接中断后重试连接的时间间隔(毫秒)。
3、必须设置必要的请求头
EventSource本质上还是Http协议,所有的Http协议都是用Http字段来跟浏览器谈心的,所以必须设置这几个header:status、content-type、cache-control、connection,不然浏览器不认识它。
4、携带参数
SSE是服务端通知客户端,客户端无法通知服务端,除非请出websocket大哥。所以只能在url上携带少量的参数标识。
5、单个浏览器最大连接数限制
当不使用 HTTP/2 时,服务器发送事件(SSE)受到打开连接数的限制,这个限制是对于浏览器的,并且设置为非常低的数字(6),打开多个选项卡时可能会特别痛苦。在 Chrome 和 Firefox 中,这个问题已被标记为“不会修复”。这个限制是每个浏览器和域名的,这意味着你可以在所有标签页中打开 6 个 SSE 连接到站点A,另外6个 SSE 连接到站点B。当使用HTTP/2 时,最大并发 HTTP 流的数量是由服务器和客户端协商的(默认为 100)。
6、避免超时断开
HTTP/1.1 协议默认开启持久连接(persistent connection),这意味着同一个 TCP 连接可以被重用来发送和接收多个 HTTP 请求和响应。然而,如果连接在一段时间内没有活动,某些中间网络设备(如防火墙、代理服务器)或客户端本身可能会认为连接已经闲置太久,从而关闭连接。所以需要定时发送心跳让浏览器别消灭它:res.write(': keep-alive\n\n')。
三、总结
总的来说,EventSource 就像一个贴心的小助手,让你的网站能够实时更新数据,而无需手动刷新页面。它操作简单、轻量实用,非常适合那些需要频繁更新的应用场景。
所以,下次当你需要让你的网页实时更新时,不妨试试 EventSource,感受一下这位小助手的神奇魔力吧!你会发现,原来实时更新也可以如此轻松愉快!
关于流式的请求,EventSource还有几个表亲戚,我们下篇再介绍他们:WritableStream、ReadableStream、TransformStream