相关文章推荐
呐喊的便当  ·  XamlParseException ...·  7 月前    · 
调皮的番茄  ·  HttpUrlConnection.getI ...·  1 年前    · 

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
    粉丝