实现效果
处理步骤
关于SSE的请求,搜集到的大多数文章都会告诉我们要用GET
的方法才可以接收流式数据,但当我们拿到一个需要用POST
方法去进行SSE请求的接口时, 此时原生EventSource
并不支持。这个时候,我们可以尝试用如下步骤进行处理:
- 首先,我们将使用
fetch
向 SSE 端发出POST
请求,响应标头要设置成ContentType: text/event-stream
。
const response = await fetch('/sse', {
method: 'POST',
headers: {
'Content-Type': 'text/event-stream'
},
body: {
"user_id": 123
}
})
- 创建一个
reader
实例来获取从服务器收到的网络请求。
// 方式1:以字符串形式接收数据,我们通过 pipelinethrough 方法将其转化成TextDecoderStream(文本解码器流,这个流可以将字节流,比如网络请求的响应,转换为Unicode字符串),再用 getReader() 方法返回一个 reader 对象。
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
// 方式2:以字节数组形式接收数据,我们直接调用 getReader()
const reader = response.body.getReader();
- 创建一个循环以继续接收消息,直到触发信号完成结束接收。
while (true) {
const {value, done} = await reader.read();
if (done) break;
console.log('Received', value);
}
在Angular应用中的代码示例
// chat.servive.ts 文件
// 中间件,用于回传信息
sseDataSubject: Subject<string> = new Subject<string>();
async postStream(param) {
const response = await fetch(yourUrl, {
method: 'POST',
headers: {
'Content-Type': 'text/event-stream'
},
body: JSON.stringify(param)
})
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
while (true) {
const {value, done} = await reader.read();
if (done) {
this.sseDataSubject.next('done'); // 传一个done的标识
break;
}
this.sseDataSubject.next(value); // 发送消息
console.log('Received', value);
}
}
// chat.component.ts 文件
constructor() {
// stream数据监听
this.chatService.sseDataSubject.subscribe(sseData => {
if (sseData === 'done') {
// loading状态消失、清除输入框内容、记录上一轮ai对话返回的信息
...
return;
}
const lastRole = this.conversionList[this.conversionList.length - 1].role;
// 未返回'done',继续拼接句子
...
// 用 ElementRef 制作滑动到底部的动画
this.scrollToBottom();
})
}
1. 为什么不用 @microsoft/fetch-event-source ?
本来按照这篇文章《Implement SSE with Spring Boot And Angular》,但结合npm的文档,其中
onmessage
方法并没有按照预期打印出返回信息。2. 为什么没有用Angular的HttpClient尝试POST请求?
在issue贴中有回复”
text/event-stream
是服务器发送事件(SSE)的媒体类型,我们不能与HttpClientModule
所基于的 XHR 请求混合。“