SSE 技术调研
体验过 ChatGPT 官方 Web 工具的同学, 应该了解, AI 对话返回的结果, 不是一次性返回的, 而是会在一段时间内, 持续输出, 就像是人在说话时, 有序地说出每一个字一样.
它是如何实现的呢, 通过 Chrome 检查工具, 可以看到, 它返回的内容类型 (Content-Type) 是
text/event-stream
, 而不是我们常用的
application/json
.
那么
text/event-stream
又是什么意思呢, 先问问 AI 吧~
原来, 它是一种实现服务器向客户端推送数据的技术, 常用于在线聊天场景^^
我以前听说过 WebSocket, 它跟 Server-Sent Events (SSE), 有什么区别和联系呢?
原来如此, 这好像一道面试题和解答呀, 哈哈, 不知道, 解答是否有问题, 是否不全面, 欢迎评论区讨论~
学习一项技术, 肯定不能只听 AI 一家之言, 还得多查点资料看看, 觉得
实时通讯技术Ajax,WebSocket,SSE
, 这篇内容写得比较全面系统, 提到 "
SSE 是 WebSocket 的一种轻量代替方案
".
SSE 在 Spring Boot + Vue 前后端分离项目中落地
我的 AI 对话演示项目 (希羽,
xiyu.zhiyuanbiji.cn/#/chat
), 后端采用 Spring Boot, 前端采用 Vue, 那么如何应用 SSE 技术, 优化对话时的交互体验呢?
回答内容比较长, 截图查看不方便, 直接复制 Markdown 内容.
🔽🔽🔽以下是 AI 自动生成的🔽🔽🔽
Spring Boot 可以通过使用 Spring WebFlux 模块来实现 Server-Sent Events。下面是一个简单的示例代码:
首先,需要在 pom.xml 文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
然后,创建一个控制器类,该类使用 SseEmitter
类来发送 Server-Sent Events:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.time.Duration;
@RestController
public class SseController {
@GetMapping("/sse")
public SseEmitter handleSse() {
SseEmitter emitter = new SseEmitter();
Flux.interval(Duration.ofSeconds(1))
.map(i -> "Server-Sent Event #" + i)
.doOnCancel(() -> emitter.complete())
.subscribe(
data -> {
try {
emitter.send(data);
} catch (IOException e) {
emitter.completeWithError(e);
error -> emitter.completeWithError(error),
() -> emitter.complete()
return emitter;
在上面的示例代码中,handleSse()
方法返回一个 SseEmitter
对象,该对象可以用来发送 Server-Sent Events。在这个方法中,我们使用 Flux.interval()
方法来生成一个每秒钟发送一次的数据流,并将其转换为一个字符串流。然后,我们使用 subscribe()
方法来订阅这个数据流,并在每次接收到数据时使用 emitter.send()
方法将数据发送给客户端。如果发生错误或订阅被取消,我们使用 emitter.completeWithError()
或 emitter.complete()
方法来关闭 SseEmitter
对象。
最后,我们可以通过访问 /sse
路径来获取 Server-Sent Events。例如,可以使用 JavaScript 来订阅这个事件流:
const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
console.log(event.data);
🔼🔼🔼以上是 AI 自动生成的🔼🔼🔼
我复制代码到项目中, 测试了一下, 发现了一个错误, 这里不能使用 @RestController
, 要用 @Controller
. 我也在网上找到了一个开源项目 Grt1228/chatgpt-java, 这位大佬已经把接口做了很好的封装, 实现了很多功能.
也给出了在 SpringBoot 项目中, 实现流式输出的 API 接口演示项目 Grt1228/chatgpt-steam-output.
演示项目包含了前后端的实现, 那就站在巨人的肩上, 迅速借鉴实现一下. 调试过程中, 还是有很多细节处理, 有一个困扰我好久的问题, 在后面会介绍, 先来看看实现后的效果吧^^
还不错吧, GIF 动画没有做任何加速处理哦~大家可以如何体验呢?
浏览器打开: xiyu.zhiyuanbiji.cn/#/chat, 需要登录
关注"知源笔记"公众号, 进入之后, 输入"666
", 直接登录
在"知源笔记"公众号中, 输入"xiaoxi
", 也可以进入 AI 对话, 但不是流式输出的, 可能因为超时, 需要输入"重试".
上线时遇到的问题
开发、测试还是挺顺利的, 效果也正常, 但发到线上以后, 一直 loading 转圈, 没有输出, 打开 Chrome 检查工具, 发现请求一直处于 pending
状态...
一直等下去, 会失败, 并报 500 错误.
这时, 我有点蒙圈, 赶紧回滚线上代码, 包括前后端[捂脸]
首先是怀疑自己的代码问题, 毕竟是第一次用 SSE, 反复查资料, 对比别人的实现; 其次怀疑是 CORS 跨域问题; 还有怀疑是 Webpack 打包的问题. 试了很多解决方案, 都没有解决问题, 有点绷不住了.
到了下午, 突然想到是不是 nginx 反向代理配置的问题, 我的域名网址 xiyu.zhiyuanbiji.cn, 是通过 nginx 反向代理到 80 端口的. 要验证这个猜想也很简单, 直接用线上 Java 项目的 ip+port 端口访问测试一下, 果然没有问题!!!
总算找到问题点了, 解决方案很快也就有了, 参考 nginx代理webSocket 和eventSource 请求超时连接不通 但是本地可以得问题 中的方案, 为了在 nginx 中支持 EventSource, 需要增加如下配置.
location /es/ {
proxy_pass http://请求地址/
proxy_set_header Connection ''
proxy_http_version 1.1
chunked_transfer_encoding off
proxy_buffering off
proxy_cache off
大功告成! 工具的交互体验大幅提升, 借助这个 AI 工具提升工作效率的同时, 使用也变得愉悦了.
最后, 打个小广告, 我最近在关注 AIGC 的技术和应用, 建了一个微信群讨论相关话题, 如果您也感兴趣, 欢迎加入^^
HenryHe
粉丝