1.SSE背景
在使用gpt时,很好奇gpt输出是一段一段输出的,并不像常用的开发server返回一个json格式的数据。当时想实现这种功能只能相当轮询server,或者websocket技术,但是没想到,还有个更好的技术:SSE服务消息推送技术
2.SSE技术介绍
SSE,全称Server-Sent Events,是一种基于HTTP协议的服务器推送技术。它允许服务器主动向客户端发送数据和信息,实现了服务器到客户端的单向通信。
2.1SSE优点
- 实时性:SSE 允许服务器主动将数据推送给客户端,实现实时更新和通知。
- 简单易用:SSE 基于标准的 HTTP 协议,无需额外的库或协议转换,WebSocket 是一个独立协议。
- 可靠性:SSE 使用 HTTP 连接,兼容性好,并能通过处理连接断开和错误情况来确保数据传输的可靠性。
- 轻量级:与 WebSocket 相比,SSE 不需要建立全双工连接,减少了通信的开销和服务器负载。
- 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
2.2使用SSE注意事项
- 服务器端应发送特定的标识来表示数据流的结束,然后前端调用close关闭EventSource。如果不这么做的话,当服务端发送完数据关闭连接后,EventSource默认会自动重新连接。
- 只支持GET:url可以携带一些简单的查询参数,如果要传输复杂的请求体,可以考虑两次请求的方案。先通过普通的HTTP POST/PUT请求,将请求体传送到服务端。服务端将请求体缓存起来,并返回一个能唯一标识的票据,前端最后使用EventSource在url中带上票据,服务端根据票据从缓存里取出请求体。
- 不支持自定义header:接口如果需要鉴权,无法在Header里定义Authorization请求头,那么建议使用Cookie来标识用户,EventSource请求会携带Cookie。
3.SSE技术实现方案
3.1server实现方案
3.1.1 请求头注意事项
服务器向浏览器发送的 SSE 数据,必须是 UTF-8 编码的文本, 并且设置以下的请求头
Content-Type: text/event-stream //事件流
Cache-Control: no-cache
Connection: keep-alive
3.1.2返回数据注意事项
返回数据格式有data、event、id、retry等字段,必须要有event否则FE接收不到数据
[field]: value\n //field字段是如上data event id retry等字段
data字段,数据很长,拆分来写,最后一条消息必须以**\n\n**结尾
data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n。
3.1.3 server代码实现
const app = express();
let text ='豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊采星驰,台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休假,胜友如云;千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电清霜,王将军之武库。家君作宰,路出名区;童子何知,躬逢胜饯。\n' +
'时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖騑于上路,访风景于崇阿。临帝子之长洲,得天人之旧馆。层峦耸翠,上出重霄;飞阁流(一作 翔)丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即(一作 列)冈峦之体势。\n' +
' 披绣闼,俯雕甍。山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。\n' +
' 遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年。\n' +
' 嗟乎!时运不齐,命途多舛;冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子见机,达人知命。老当益壮,宁移白首之心?穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹欢。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。孟尝高洁,空余报国之情;阮籍猖狂,岂效穷途之哭!\n' +
' 勃,三尺微命,一介书生。无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;钟期既遇,奏流水以何惭?\n' +
' 呜呼!胜地不常,盛筵难再;兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙怀,恭疏短引;一言均赋,四韵俱成。请洒潘江,各倾陆海云尔。'
let arr = text.splite(/[,。:!?]/g);
app.get('/serverSseApi',(req,res)=>{
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type','text/event-stream');
res.setHeader('Cache-Control','no-cache');
res.setHeader('Connection','keep-alive');
setInterval(()=>{
if(arr.length>0){
res.write('event: message\n');
res.write('data: '+arr.shift()+'\n');
res.write('data: '+arr.shift()+'\n\n');
}
},Math.floor(Math.random() * (800 - 300 + 1)) + 300);
});
app.listen(7070,()=>{
console.log('Server is running on port 7070');
});
3.2 FE实现方案
FE可以看看MDN官网写的,比较详细MDN-eventSource
const eventSource = new EventSource('http://localhost:7070/serverSseApi');
eventSource.onmessage = (event) => {
console.log(event.data); // 处理接收到的事件数据
const { content } = this.state;
this.setState({
content: content+event.data,
});
};
eventSource.onerror = function(error) {
console.error('Error with SSE connection:', error); // 处理连接错误
let timer =setTimeout(()=>{
eventSource.close();//短线重连不上就关闭
clearTimeout(timer);
},1000*30);
};
总结
SSE只能说相见恨晚啊,果然实习还是能学到真东西,至少能了解某些技术叫什么,怎么实现的。