《当 AI 开始“打字机”:Web 流式输出(SSE)的魔幻漂流》

0. 开场白:为什么 AI 喜欢"挤牙膏"?

想象一下,如果 AI 一次性把整篇《三体》丢给你,浏览器会像被三体人智子锁死的加速器------卡成 PPT

于是,人类发明了 Server-Sent Events(SSE) ,让 AI 像老式打字机一样,哒哒哒地边想边打,既优雅又省内存。


1. SSE 的底层原理:HTTP 的"马拉松"模式

1.1 从"问答"到"长聊"

普通 HTTP 是 "你一句我一句" 的短对话:

js 复制代码
浏览器:GET /answer?q=宇宙的意义
服务器:42(完)

SSE 则是 "服务器单方面开麦" 的脱口秀:

js 复制代码
浏览器:GET /stream?q=宇宙的意义
服务器:4... (别关麦!)
服务器:2... (还在想!)
服务器:! (终于想完了)

1.2 底层魔法:HTTP/1.1 的"不死连接"

SSE 本质是 一个永不关闭的 HTTP 连接,靠以下 HTTP 头实现:

http 复制代码
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

就像你给服务员说:"咖啡续杯到天荒地老。"


2. 前端:如何优雅地"偷听"AI 碎碎念?

2.1 EventSource:浏览器自带的"监听器"

js 复制代码
const stream = new EventSource('/ai/stream?q=如何优雅地摸鱼');

stream.onmessage = (event) => {
  // AI 每吐一个字,这里就收到一次
  document.body.innerHTML += event.data;
};

stream.onerror = () => {
  // AI 断线了(可能去喝咖啡了)
  console.error('AI 掉线了,正在重连...');
};

2.2 手动解析 SSE 格式(硬核玩家)

SSE 数据格式像 "键值对+换行" 的极简诗:

text 复制代码
data: 你
data: 好
data: 世
data: 界

event: close
data: (说完收工)

用 JS 手动解析:

js 复制代码
const decoder = new TextDecoder();
const reader = response.body.getReader();

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  
  const chunk = decoder.decode(value);
  const lines = chunk.split('\n');
  
  for (const line of lines) {
    if (line.startsWith('data: ')) {
      console.log('AI说:', line.slice(6));
    }
  }
}

3. 后端:如何让服务器变成"话痨"?

3.1 Node.js 极简实现(Express 版)

js 复制代码
const express = require('express');
const app = express();

app.get('/ai/stream', (req, res) => {
  // 设置 SSE 响应头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // 每秒发一个字(假装 AI 在思考)
  const message = '你好,我是 AI,正在用 0.3 倍速思考人生...';
  let index = 0;

  const interval = setInterval(() => {
    if (index >= message.length) {
      res.write('event: close\ndata: \n\n');
      clearInterval(interval);
      res.end();
      return;
    }

    res.write(`data: ${message[index]}\n\n`);
    index++;
  }, 100);
});

app.listen(3000, () => console.log('AI 电台已开播:http://localhost:3000'));

3.2 处理"断线重连"的哲学问题

浏览器会在 连接断开时自动重连(默认 3 秒),后端需要优雅处理:

js 复制代码
// 客户端重连时,从上次位置继续
const lastEventId = req.headers['last-event-id'];
if (lastEventId) {
  // 从第 N 个字开始续播
  index = parseInt(lastEventId, 10);
}

4. 高阶技巧:让 AI 像 Rapper 一样押韵

4.1 压缩输出(节省带宽)

gzip 压缩 SSE 数据(需前端配合):

js 复制代码
res.setHeader('Content-Encoding', 'gzip');

4.2 多路复用(一个连接 N 个话题)

SSE 原生只支持 单通道 ,但可以通过 event: 字段模拟多路:

text 复制代码
event: joke
data: 为什么 AI 不失眠?因为它没有床。

event: answer
data: 因为 42。

前端区分事件:

js 复制代码
stream.addEventListener('joke', (e) => {
  console.log('AI 讲了个笑话:', e.data);
});

5. 彩蛋:SSE 的"暗黑料理"

5.1 用 SSE 实现"服务器推送广告"

js 复制代码
setInterval(() => {
  res.write('event: ad\ndata: 买它!AI 同款显卡只要 999!\n\n');
}, 5000);

5.2 用 SSE 做"实时弹幕"

前端用 CSS 让文字从右向左飞:

css 复制代码
.barrage {
  position: fixed;
  white-space: nowrap;
  animation: fly 5s linear;
}

@keyframes fly {
  from { transform: translateX(100vw); }
  to { transform: translateX(-100%); }
}

6. 结语:当 AI 学会"拖延术"

SSE 的本质是 "用时间换空间" ------

让用户等 5 秒,但能省 50MB 内存。

就像你老板说的:"慢慢做,做得慢才不会出错。"

"AI 不是变快了,只是学会了优雅地拖延 。"

------某 SSE 协议开发者,在 404 酒吧的酒后真言


附录:参考资料

  1. MDN: Server-Sent Events
  2. WhatWG SSE 标准
相关推荐
天才熊猫君43 分钟前
npm 和 pnpm 的一些理解
前端
飞飞飞仔44 分钟前
从 Cursor AI 到 Claude Code AI:我的辅助编程转型之路
前端
qb1 小时前
vue3.5.18源码:调试方式
前端·vue.js·架构
Spider_Man1 小时前
缓存策略大乱斗:让你的页面快到飞起!
前端·http·node.js
前端老鹰1 小时前
CSS overscroll-behavior:解决滚动穿透的 “边界控制” 专家
前端·css·html
一叶怎知秋1 小时前
【openlayers框架学习】九:openlayers中的交互类(select和draw)
前端·javascript·笔记·学习·交互
allenlluo2 小时前
浅谈Web Components
前端·javascript
Mintopia2 小时前
把猫咪装进 public/ 文件夹:Next.js 静态资源管理的魔幻漂流
前端·javascript·next.js
Spider_Man2 小时前
预览一开,灵魂出窍!低代码平台的魔法剧场大揭秘🎩✨
前端·低代码·typescript
xianxin_2 小时前
HTML 代码编写规范
前端