SSE(server-sent events)是什么

MDN上有关于SSE的介绍,可以把SSE理解为单向的WebSocket。普通请求 + WebSocket + SSE联手一起完善了浏览器网络请求体系。它们的区别如下:

  • 普通请求(GET,POST,PUT等等):浏览器--->服务器,单向非持续数据流。
  • WebSocket:浏览器<--->服务器,双向持续数据流。
  • SSE:浏览器<---服务器,单向持续数据流。

SSE的表现形式上和WebSocket差不多,依旧由浏览器主动发起请求,但是服务端不会立即返回,而是保持长连接,伺机返回数据,相应的,浏览器端则需要监听事件。

场景解析

知道了SSE能实现什么功能,你可能已经跃跃欲试了,但是SSE的使用场景是什么呢?为什么不直接使用WebSocket呢? 一个经典的使用场景就是AI聊天。在用户输入提问后,AI会流式地返回消息,所以浏览器发送提问后,就只需要持续接受消息,不需要再发送消息了。 以下内容以AI聊天为切入点,详解Nuxt3中如何实现这类需求。

需求

假设我们有一个api_key,用这个key可以和ChatGPT聊天,需求是:开发一个web应用,让所有访问者都可以和ChatGPT聊天,而用户不需要知道key是什么。简单来说,就是实现一个ChatGPT套壳。(假设ChatGPT的接口地址为https://chatgpt.com/chat是基于SSE的)

相关工具

在网上搜索后,我找到了以下信息/文档:

  • Nuxt3目前已经原生支持SSE,提供createEventStream()函数,详见官方样例
  • 一个很有价值的gist参考,用hookable实现NuxtServer的api间通信:gist
  • 一个npm包,可以在nodejs里方便地创建SSE客户端:@fortaine/fetch-event-source

流程解析

流程图如下(第一次用obsidian的excalidraw做这么复杂的图,做得有点差,见谅):

流程详解:

  1. 浏览器生成一个随机uid,用来区分其他聊天。uid是必须的,否则无法区分聊天。
  2. 浏览器请求/api/start-sse接口,携带uid和prompt。
  3. NuxtServer收到请求,开启一个fetch()请求ChatGPT的服务器,携带api_key和prompt(Nuxt作为sse的client)。随即返回{ success: true }
  4. 浏览器开启一个EventSource(),接口地址是/api/sse,同时也携带uid。
  5. NuxtServer收到请求,创建一个eventStream并返回(Nuxt作为sse的server),浏览器收到响应,触发onopen事件。
  6. 在第3步中,NuxtServer开启了一个fetch(),其响应是流式的,每次收到响应都会调用callHook(),触发/api/sse中的hook(),最终push()推送消息到了浏览器。

样例代码

我写了一个简单的示例项目,开源在Github,项目里不会真的调用ChatGPT,而是用Nuxt另外起了一个SSE接口,每隔一秒返回当前时间: