文章目录
- SSE网络协议
- 客户端
- 服务端
- 事件
- SSE示例
- 客户端
- 服务端
SSE(Server-Sent Events)是一种服务端到客户端(浏览器)的单向消息推送方式。
SSE网络协议
SSE是基于HTTP协议的,客户端向服务端发起一个请求,建立长连接( keep-alive connection);服务端向客户端发送(应答)不是一次性的包,而是一个数据流。
客户端
要实现SSE协议,客户端发起的请求头中需要携带:
Accept: text/event-stream
: 表示可接收事件流类型Cache-Control: no-cache
: 禁用任何的事件缓存Connection: keep-alive
: 表示正在使用持久连接
如:
GET /sse HTTP/1.1 Accept: text/event-stream Cache-Control: no-cache Connection: keep-alive
SSE默认支持断线重连机制,在连接断开时会触发EventSource的error事件,同时自动重连。
服务端
服务端应答头中需要包含:
Content-Type: text/event-stream;charset=UTF-8
: 表示标准要求的事件的媒体类型和编码Transfer-Encoding: chunked
: 表示服务器流式传输动态生成的内容,因此内容大小事先未知
如:
HTTP/1.1 200 Content-Type: text/event-stream;charset=UTF-8 Transfer-Encoding: chunked
事件
事件采用UTF-8编码的文本消息:
- 事件之间由两个换行符
\n\n
分隔; - 每个事件由一个或多个
{key}: {value}
字段组成;字段间由单个换行符\n
分隔。 - 若某行以冒号
:
开始,客户端应忽略:可用于防止中间代理因超时关闭连接;如:ping
。
规范中定义了事件的四种字段:
- retry:表示超时重连间隔(毫秒);
- data:表示包含的是数据,可多次出现;
- event:表示事件的类型(若不写,默认为message),浏览器会生成对应类型的事件;
- id:表示事件标识符;
id: 1
event: chat
retry: 3000
data: firstid: 2
event: chat
retry: 3000
data: second
data: second continue
注意:如果服务器端返回的数据中包含了事件的标识符id,浏览器会记录最近一次接收到的事件的标识符。当浏览器因断开重连时,会通过HTTP头Last-Event-ID
来声明最后一次接收到的事件的标识符;服务器端可根据此标识符确定从哪个事件开始来继续连接。
SSE示例
客户端
客户端SSE是在EventSource中实现的,EventSource内置了3个EventHandler属性、2个只读属性和1个方法:
- onopen属性:在连接打开时被调用。
- onmessage属性:在收到一个没有event属性的消息时被调用。
- onerror属性:在连接异常时被调用。
- readyState只读属性:代表连接状态;可能值是
CONNECTING(0),OPEN(1),CLOSED(2)
。 - url只读属性:连接的URL。
- close()方法:关闭连接
'use strict';if (window.EventSource) {// 创建 EventSource 对象连接服务器const source = new EventSource('http://localhost:2000/stream');// 连接成功后会触发 open 事件source.addEventListener('open', () => {console.log('Connected');}, false);// 服务器发送信息到客户端时,如果没有 event 字段,默认会触发 message 事件source.addEventListener('message', e => {console.log(`data: ${e.data}`);}, false);// 自定义 EventHandler,在收到 event 字段为 slide 的消息时触发source.addEventListener('slide', e => {console.log(`data: ${e.data}`); // => data: 7}, false);// 连接异常时会触发 error 事件并自动重连source.addEventListener('error', e => {if (e.target.readyState === EventSource.CLOSED) {console.log('Disconnected');} else if (e.target.readyState === EventSource.CONNECTING) {console.log('Connecting...');}}, false);
} else {console.error('Your browser doesn\'t support SSE');
}
服务端
服务端使用基于Flask的实现
from flask import Flask, request
from flask import Response
from flask import render_templateapp = Flask(__name__)def get_message():"""this could be any function that blocks until data is ready"""time.sleep(1)s = time.ctime(time.time())return json.dumps(['当前时间:' + s , 'a'], ensure_ascii=False)@app.route('/')
def hello_world():return render_template('index.html')@app.route('/stream')
def stream():user_id = request.args.get('user_id')print(user_id)def eventStream():id = 0for i in range(10):id +=1# wait for source data to be available, then push ityield 'id: {}\nevent: add\ndata: {}\n\n'.format(id,get_message())id +=1yield 'id: {}\nevent: done\n\n'.format(id) return Response(eventStream(), mimetype="text/event-stream")if __name__ == '__main__':app.run(port=2000)