HTTP 服务器推送也称 HTTP 流,是一种客户端-服务器通信模式,它将信息从 HTTP 服务器异步推送到客户端,而无需客户端请求。现在的 web 和 app 中,越来越多的场景使用这种通信模式,比如实时的消息提醒,IM在线聊天,多人文档协作等。以前实现这种类似的功能一般都是用ajax长轮询,而现在我们有了新的、更优雅的选择 —— WebSocket 和 SSE。
WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
SSE 是 Server-Sent Events 的简称, 是一种服务器端到客户端(浏览器)的单项消息推送。对应的浏览器端实现 Event Source 接口被制定为HTML5 的一部分。不过现在IE不支持该技术。相比于 WebSocket,SSE 简单很多,服务器端和客户端工作量都要小很多、简单很多,同时实现的功能也要有局限。
SSE&WebSocket
SSE与WebSocket有相似功能,都是用来建立浏览器与服务器之间的通信渠道。两者的区别在于:
- WebSocket是全双工通道,可以双向通信,功能更强;SSE是单向通道,只能服务器向浏览器端发送。
- WebSocket是一个新的协议,需要服务器端支持;SSE则是部署在 HTTP协议之上的,现有的服务器软件都支持。
- SSE是一个轻量级协议,相对简单;WebSocket是一种较重的协议,相对复杂。
- SSE默认支持断线重连,WebSocket则需要额外部署。
- SSE支持自定义发送的数据类型。
- SSE不支持CORS,参数url就是服务器网址,必须与当前网页的网址在同一个网域(domain),而且协议和端口都必须相同。WebSocket支持
客户端 EventSource
API
[Constructor(DOMString url, optional EventSourceInit eventSourceInitDict)] interface EventSource : EventTarget { readonly attribute DOMString url; readonly attribute boolean withCredentials; // ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSED = 2; readonly attribute unsigned short readyState; // networking attribute EventHandler onopen; attribute EventHandler onmessage; attribute EventHandler onerror; void close(); }; dictionary EventSourceInit { boolean withCredentials = false; };
基本用法
- 创建EventSource 实例
var source = new EventSource(url)
- 事件监听
// 建立连接后,触发`open` 事件 source.onopen = (event)=>{ // ... } // 收到消息,触发`message` 事件 source.onmessage = (event)=>{ // ... } // 发生错误,触发`error` 事件 source.onerror = (event)=>{ // ... } // 自定义事件 source.addEventListener('eventName', event => { // ... }, false)
- 关闭连接
source.close()
服务器端开发
响应头设置
SSE的相应,需要设置如下的Http头信息
Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive
第一行,Content-Type
指定的 MIME
类型必须为 text/event-stream
消息格式
SSE 推送的消息必须是UTF-8
编码的纯文本。每次推送有若干个事件消息组成,每个事件消息之间用两个换行(\n\n
)分割。每个事件消息又有若干行组成,每行格式以键值对形式组成:
[key]: value\n
key
有一下几个值可取
- data: 消息内容
- event: 消息事件名称,默认为 message,浏览器可以用
addEventListener()
监听该事件。
- id: 消息编号。浏览器用lastEventId属性读取这个值。一旦连接断线,浏览器会发送一个 HTTP 头,里面包含一个特殊的Last-Event-ID头信息,将这个值发送回来,用来帮助服务器端重建连接。因此,这个头信息可以被视为一种同步机制
- retry: 浏览器重新发起连接的时间间隔。
示例
浏览器端代码
// index.js var source = new EventSource('/stream'); source.onmessage = function(event) { var message = event.data; // do stuff based on received message };
服务器端代码(nodejs)
var express = require('express') var fs = require('fs') var app = express() app.get('/stream', (req, res) => { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" }); var interval = setInterval(function () { res.write("data: " + (new Date()) + "\n\n"); }, 1000); req.connection.addListener("close", function () { clearInterval(interval); }, false); }) app.listen(9999, (err) => { if (err) { console.log(err) return } console.log('listening on port 9999') })