记录一次EventSourse请求(falsk+react)
背景
需求需要后端有新的数据时传给前端,因为新的数据不确定什么适合会来,用轮询有点浪费.后来考虑用websocket,wb用起来太复杂,便发现了前端接口eventsource,建立连接后后端单方面给前端传数据就行,简单好用.
后端是用flask写的,前端用的是react
实现
前端建立连接并接收数据
typescript
// 建立连接
const eventSource = new EventSource("/es");
// 连接成功
eventSource.onopen = function () {
console.log("open")
}
// 接受消息
eventSource.onmessage = function (ev) {
console.log("event")
console.log(ev.data)
};
// 发生错误
eventSource.onerror = function () {
console.log("error");
eventSource.close();
};
后端响应数据
后端实现很简单,将响应的数据类型设置为text/event-stream
就可以,然后多次响应,就可以多次像前端传数据了
python
@app.route("/es", methods=["GET"])
def es():
def eventStream():
while True:
time.sleep(1)
yield "test"
return Response(eventStream(), mimetype="text/event-stream")
起初这样写,通过接口工具测试时没问题,可以正常接收响应,但是在浏览器里却不正常显示.搜索发现前端需要接受特定的格式内容id:1\nevent:message\ndata:test\n\n
,id:可以省略,event:message是固定的,用其他名称的话需要前端自定义(这里没有深入研究),data:是固定的要传输的数据的格式,于是做了改进,这样浏览器就可以接收到数据内容了
python
@app.route("/es", methods=["GET"])
def es():
def eventStream():
while True:
time.sleep(1)
yield "event: message\ndata:test"\n\n"
return Response(eventStream(), mimetype="text/event-stream")
优化
测试发现,前端关闭连接后,后端还在持续响应数据.于是在前端请求数据时增加了link_id,关闭连接时调用后端提供的关闭连接接口来停止后端的响应.
前端使用useEffect(),进入时请求连接,离开时请求关闭
typescript
// 生成uuid
const linkId = useId()
useEffect(() => {
const eventSource = new EventSource("/es?link_id=" + linkId);
eventSource.onopen = function () {
console.log("open")
}
eventSource.onmessage = function (ev) {
console.log("event")
console.log(ev.data)
};
eventSource.onerror = function () {
console.log("error");
eventSource.close();
};
// 关闭连接时
return () => {
axios.get("/es/close?link_id=" + linkId).then((res) => {
console.log(res.data)
})
eventSource.close()
}
}, []);
后端使用一个集合来存放前端请求时的link_id,请求关闭时将link_id加入集合.检测到当前请求的link_id在集合中时便关闭连接
typescript
# 集合用于存放link_id
closeSets = set()
# 关闭连接请求
@app.route("/es/close", methods=["GET"])
def closeEvent():
linkId = request.args.get("link_id")
closeSets.add(linkId)
return "success"
# 请求连接
@app.route("/es", methods=["GET"])
def es():
linkId = request.args.get("link_id")
def eventStream(linkId):
while True:
# 关闭连接
if linkId in closeSets:
closeSets.remove(linkId)
break
time.sleep(1)
yield "event: message\ndata:test"\n\n"
return Response(eventStream(linkId), mimetype="text/event-stream")
参考
segmentfault.com/q/101000004...
segmentfault.com/q/101000004...