什么是 sse
SSE(Server-Sent Events)是一种基于 HTTP 协议,用于实现服务器主动向客户端推送数据的技术。它在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送,而客户端不能发送数据给服务端。
SSE是一种允许服务器向客户端单向发送数据的技术。
和 websocket 不同 ,websocket 是客户端和服务器可以双向发送数据,sse 技术常用与 消息推送,websocket 常用与 聊天
为什么选择 sse
我最近写的是模仿 ai 的一个流式输出,前端需要做的就只是接收 后端所传递过来的若干数据,并且实现流式输出,重点是 : 我们只用接收,而不必去向服务器发送数据
使用 sse
sse 和 websocket 的方法差不多 提供的也是几个回调函数 onopen、onmessage、onerror 、onclose
vue3+ts 建立sse连接代码
ini
const eventSource = ref<EventSource | null>();
const initSSE = () => {
eventSource.value = new EventSource("http://地址");
eventSource.value.onmessage = (event) => {
console.log("收到消息内容是:", event.data);
};
eventSource.value.onerror = (error) => {
console.error("SSE 连接出错:", error);
eventSource.value!.close();
};
};
onMounted(() => {
initSSE();
});
onUnmounted(() => {
if (eventSource) {
eventSource.value?.close();
}
});
然后 在实际运用过程中,我发现如上代码是get请求,实际开发中是 post 请求。sse 本身不支持 post 请求 。
于是我们需要借助 fetchEventSource 来实现 sse 的 post 请求
使用 fetchEventSource 建立 sse 的 post 请求
安装
css
pnpm i @microsoft/fetch-event-source
使用
javascript
await fetchEventSource(
getForumAIUrl("你的地址",
{
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify({
//你的数据,也可以不传
}),
openWhenHidden: true,
onopen() {},
onmessage(event) {
isLoading.value = false;
let data = JSON.parse(event.data);
//处理业务逻辑
},
onerror(error) {
console.error("SSE 连接出错:", error);
},
}
);
以上就是建立了一个 sse 的 post 连接
但是我在写代码的过程中 ,又发现很多地方都需要用到这个 sse 技术,能不能给他封装起来,以便于我们更好的使用,于是我便开始了封装之路
封装 sse
思路: 考虑到 我们其实只需要传递 需要的url,方法,以及数据,有时候需要 在header 中设置 一些 key 来访问,并且我们大部分业务逻辑只关心 onmessage 事件的触发 和使用
在 utils 目录下 新建了 一个 sse.ts 文件
以下是所有的代码
typescript
import { fetchEventSource } from "@microsoft/fetch-event-source";
import { ElMessage } from "element-plus";
// 定义消息回调类型
type MessageHandler = (data: any) => void;
export class SSEService {
async connect(
url: string,
method: "GET" | "POST",
body: object,
onMessageHandler: MessageHandler,
myHeader?: Record<string, string> // 键值对的形式
) {
return await fetchEventSource(url, {
method: method,
headers: {
"Content-Type": "application/json;charset=utf-8",
...myHeader, // 当 myHeader 未定义时会自动忽略展开
},
body: JSON.stringify(body),
openWhenHidden: true,
async onopen() {
// 可以在这里添加连接成功逻辑
},
onmessage(event) {
onMessageHandler(event);
},
onerror(error) {
ElMessage.error("SSE连接出错: " + error);
},
onclose(){
}
});
}
}
通过封装成 SSEService 类,并且在触发 onmessage 事件时,调用 我们传递进去的 函数,这样我们就可以使用,并且支持 headers 添加自己想要的内容
使用示例代码
ini
import { SSEService } from "@/utils/sse";
let sse = new SSEService();
sse.connect(
getForumAIUrl(currentConversition.value!.id) +
`?search_enabled=${isInternet.value}`,
"POST",
{
query: sendText.value,
stream: true,
history: [],
},
(event: any) => {
// 这里处理收到的消息
let data = JSON.parse(event.data);
console.log("收到SSE消息:", data);
//下面的业务逻辑可以替换成你自己的
isLoading.value = false;
if (data?.done) {
return;
}
if (data?.content) {
let decodeContent = decodeUnicode(data!.content);
console.log(decodeContent);
handleRender(decodeContent);
} else {
setTimeout(() => {
isLoading.value = false;
});
}
}
);
然后这就封装好并且使用了,不用每次都写 冗余的代码去建立 sse 连接了