一.SSE原理介绍
1、SSE基础概念
SSE(Server Sent Event),直译为服务器发送事件,顾名思义,也就是客户端可以获取到服务器发送的事件。我们常见的 http 交互方式是客户端发起请求,服务端响应,然后一次请求完毕;但是在 sse 的场景下,客户端发起请求,连接一直保持,服务端有数据就可以返回数据给客户端,这个返回可以是多次间隔的方式
2、SSE特点
从 sse 的特点出发,我们可以大致的判断出它的应用场景,需要轮询获取服务端最新数据的 case 下,多半是可以用它的。比如显示当前网站在线的实时人数,法币汇率显示当前实时汇率,电商大促的实时成交额等等
3、SSE使用场景
在web端消息推送功能中,由于传统的HTTP协议是由客户端主动发起请求,服务端才会响应。基本的ajax轮询技术便是如此。而在SSE中,浏览发送一个请求给服务端,通过响应头中的Content-Type:text/event-stream等向客户端声名这是一个长连接,发送的是流数据,这样客户端就不会关闭连接,一直等待服务端发送数据。
如果服务器返回的数据中包含了事件标识符,浏览器会记录最后一次接收的事件的标识符。如果与服务器的连接中断,当浏览器再次进行连接时,会通过头来声明最后一次接收的事件的标识符。服务器端可以通过浏览器发送的事件标识符来确定从哪个事件来继续连接
4、SSE注意事项
保证数据的完整性 :客户端在每次接收到消息时,会把消息的id 字段作为内部属性 Last-Event-ID储存起来。SSE默认支持断线重连机制,在连接断开时会 触发EventSource的error事件,同时自动重连。再次连接成功时 EventSource会把Last-Event-ID属性作为请求头发送给服务器,这样服务器就可以根据这个Last-Event-ID作出相应的处理。这里需要注意⚠️的是,id字段不是必须的,服务器有可能不会在消息中带上id字段,这样子客户端就不会存在Last-Event-ID这个属性。所以为了保证数据可靠,我们需要在每条消息上带上id字段。
SSE不支持IE浏览器
5、实战
1.get方式实现
前端
if(window.EventSource){
// 根据环境的不同,变更url
const url = config.host2;
var that=this;
this.eventSource=new EventSource(`${url}/see`,{msg:this.curMessage});
this.eventSource.onmessage = (e) => {
const message = JSON.parse(e.data);
//this.messages.push(message);
console.log("已接受到消息:", e.data)
};
this.eventSource.onerror = (event) => {
that.eventSource.close();
};
//处理服务器响应报文中的自定义事件
this.eventSource.addEventListener("close", function (e) {
that.eventSource.close();
});
} else {
console.log("你的浏览器不支持SSE~")
}
后端
[HttpGet("sse")]
public async Task<IActionResult> GetChatResponseSSE(string message)
{
var response = await _chatGPTService.GetChatResponse(message);
Response.Headers.Add("Cache-Control", "no-cache");
Response.Headers.Add("Content-Type", "text/event-stream");
await Response.StartAsync();
var lines = response.Split('\n');
foreach (var line in lines)
{
if (string.IsNullOrEmpty(line))
{
await Response.WriteAsync("\n");
}
else
{
await Response.WriteAsync($"data: {line}\n\n");
await Response.Body.FlushAsync();
}
}
return new EmptyResult();
}
2.post实现
sse本身是不支持post的方式,可以通过fetch的方式完成post相关操作。具体可以使用开源组件完成需求:https://github.com/Azure/fetch-event-source
前端
const controller = new AbortController()
const signal = controller.signal
fetchEventSource(`${url}`, {
method: 'POST',
signal: signal,
headers: {
'Content-Type': 'application/json',
token: window.sessionStorage.getItem('token'),
},
body:JSON.stringify(message),
onmessage(msg) {
if(msg.event === ''){
var result= JSON.parse(msg.data);
}
else if (msg.event === 'close') {
controller.abort();
}
},
onerror(err) {
throw err; //必须throw才能停止
}
});
后端
[HttpGet("sse")]
public async Task<IActionResult> GetChatResponseSSE(string message)
{
var response = await _chatGPTService.GetChatResponse(message);
Response.Headers.Add("Cache-Control", "no-cache");
Response.Headers.Add("Content-Type", "text/event-stream");
await Response.StartAsync();
var lines = response.Split('\n');
foreach (var line in lines)
{
if (string.IsNullOrEmpty(line))
{
await Response.WriteAsync("\n");
}
else
{
await Response.WriteAsync($"data: {line}\n\n");
await Response.Body.FlushAsync();
}
}
return new EmptyResult();
}