SSE请求多种实现方式总结
- 什么是SSE
- 一、怎么实现SSE请求(基础版本)
- [二、Fetch API实现SSE(升级版本)](#二、Fetch API实现SSE(升级版本))
-
- [1、 node后端代码](#1、 node后端代码)
- [2、 前端Fecth请求实现](#2、 前端Fecth请求实现)
- 3、特点
- 三、Fecth结合EventSource实现SSE(终极版本)
- 四、总结
文前推荐一下👉
前端必备工具推荐网站(图床、API和ChatAI、智能AI简历、AI思维导图神器等实用工具):
什么是SSE
SSE(Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为"事件流"(Event Stream)。它基于 HTTP 协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。
SSE 和 Socket 区别
SSE(Server-Sent Events)和 WebSocket 都是实现服务器向客户端实时推送数据的技术,但它们在某些方面还是有一定的区别。
适用于场景
chatGPT 返回的数据 就是使用的SSE 技术
实时数据大屏 如果只是需要展示 实时的数据可以使用SSE技术 而不是非要使用webSocket
一、怎么实现SSE请求(基础版本)
1、前端实现:
EventSource 对象是 HTML5 新增的一个客户端 API,用于通过服务器推送实时更新的数据和通知。在使用 EventSource 对象时,如果服务器没有正确地设置响应头信息(如:Content-Type: text/event-stream),可能会导致 EventSource 对象无法接收到服务器发送的数据。
前端示例代码
js
const sse = new EventSource('http://localhost:3000/api/sse' )
sse.addEventListener('open', (e) => {
console.log(e.target)
})
//对应后端nodejs自定义的事件名lol
sse.addEventListener('lol', (e) => {
console.log(e.data)
})
2、 nodejs 后端示例代码
js
import express from 'express';
const app = express();
app.get('/api/sse', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream', //核心返回数据流
'Connection': 'close'
})
const data = fs.readFileSync('./index.txt', 'utf8')
const total = data.length;
let current = 0;
//mock sse 数据
let time = setInterval(() => {
console.log(current, total)
if (current >= total) {
console.log('end')
clearInterval(time)
return
}
//返回自定义事件名
res.write(`event:lol\n`)
/返回数据
res.write(`data:${data.split('')[current]}\n\n`)
current++
}, 300)
})
app.listen(3000, () => {
console.log('Listening on port 3000');
});
上面./index.txt的内容就是一些随机的测试文本,如下
text
悲索之人烈焰加身,堕落者 不可饶恕,我即是引路的灯塔,也是净化的清泉。
永恒燃烧的羽翼,带我脱离凡间的沉沦,圣火将你洗涤。
今由烈火审判,于光明中得救。
利刃在手,制裁八方!
3、特点
这是一个最基础的实现版本,但是存在一个问题:这种sse的实现方式只能是GET请求,所以对参数传递的长度会有严重的限制
比如在AI聊天场景这种方式就不太适合,其实我们也可以通过浏览器Fetch API实现SSE
二、Fetch API实现SSE(升级版本)
fetch 本身不直接支持流式输出,但你可以使用 ReadableStream 和 TextDecoder 等 Web Streams API 来实现类似的效果。
1、 node后端代码
代码如下(示例):
js
router.get("/sse", (req: Request, res: Response)=>{
console.log('/sse')
res.writeHead(200, {
'Content-Type': 'text/event-stream', //核心返回数据流
'Connection': 'close'
})
const data = fs.readFileSync(path.join(__dirname, '../../source/index.txt'), 'utf8');
const total = data.trim().length;
let current = 0;
//mock sse 数据
const time = setInterval(() => {
console.log(current, total)
if (current >= total) {
console.log('end')
res.write(`event:end\n`)
res.write(`data:\n\n`);
clearInterval(time)
return
}
//返回自定义事件名
res.write(`event:lol\n`)
// 返回数据
res.write(`data:${data.split('')[current]}\n\n`);
current++
}, 50)
});
2、 前端Fecth请求实现
代码如下(示例):
js
function streamOutput(msg) {
// 发送 POST 请求
fetch('/sse', {
method:"POST",
body:JSON.stringify({ "content": msg}),
timeout: 0,
dataType:"text/event-stream",
headers:{
"Content-Type":"application/json"
},
}).then(response => {
// 检查响应是否成功
if (!response.ok) {
throw new Error('Network response was not ok');
}
// 返回一个可读流
return response.body;
}).then(body => {
disableLoading();
const reader = body.getReader();
// 读取数据流
function read() {
return reader.read().then(({ done, value }) => {
// 检查是否读取完毕
if (done) {
console.log('已传输完毕');
return;
}
// 处理每个数据块
console.log('收到的数据:', value);
// 继续读取下一个数据块
read();
});
}
// 开始读取数据流
read();
}).catch(error => {console.error('Fetch error:', error);});
}
3、特点
这种方式的优点很直接可以支持POST请求方式来实现SSE效果,而且请求参数长度可以得到很大的拓展,符合长文本输入的需求.另外Fetch是浏览器原生API支持度好,简单易用.
三、Fecth结合EventSource实现SSE(终极版本)
这种方式结合了两种实现方式,是不是很特别,他的实现类似Websoket,后端需要通过保存前端的EventSource 队列来管理,我们直接上代码
1、node后端代码示例
js
const express = require('express');
const router = express.Router();
let clients = [];
// 创建一个 SSE 端点
router.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 发送初始消息
res.write('data: {"status": "connected"}\n\n');
// 将客户端添加到监听列表
clients.push(res);
req.on('close', () => {
// 客户端断开连接时从列表中移除
clients = clients.filter(client => client !== res);
console.log('Client disconnected');
});
// 保持连接打开
res.on('close', () => {
clients = clients.filter(client => client !== res);
});
});
// 生成 PPT 并发送更新到所有客户端
router.post('/sse', async (req, res) => {
let markdownContent = `这是语段测试文本\n\n成东南侧不对称\n\n呢哈哈哈哈哈哈哈哈`;
const slideContents = markdownContent.split('\n\n');
for (let slideContent of slideContents) {
const event = JSON.stringify({
finish: false,
content: slideContent
});
// 发送消息到所有连接的客户端
clients.forEach(client => {
if (client.writable) {
client.write(`data: ${event}\n\n`);
}
});
}
// 完成后可以发送一个完成消息,但通常 SSE 不需要这样做
// 因为客户端可以通过关闭连接来检测完成
let endEvent = JSON.stringify({
finish: true,
content: ''
});
client.write(`data: ${endEvent}\n\n`);
});
module.exports = router;
2、前端代码示例
html
<script>
// SSE连接
var evtSource = new EventSource('/events');
evtSource.onmessage = function(e) {
console.log("EventSource收到信息:", e.data)
};
evtSource.onerror = function(e) {
console.error('EventSource failed:', e);
evtSource.close();
};
function sendMesssageFn() {
// 使用fetch API发送POST请求
fetch('/sse', {
method: 'POST', // 或者 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data), // 必须是JSON字符串
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text(); // 或者返回response.json()如果后端返回JSON
})
.then(text => {
// 这里假设后端返回纯文本消息
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
}
sendMesssageFn();
</script>
3、特点
这种方式虽然也是通过fecth进行信息请求通信,但是不同的是他的消息监听仍然是通过EventSource实现的,所以不需要通过getReader解析信息.
四、总结
SSE是一种单工的通信方式,实现方式十分多样,每一种实现都有各自的优点缺点,应该根据需求进行合理的选择.