Nuxt3创建SSE server以及client的简明教程

2024-07-06404

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 做这么复杂的图,做得有点差,见谅sticker):

  流程详解:

  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 接口,每隔一秒返回当前时间:

评论