当你使用 ChatGPT、豆包或 Claude 时,是否注意到回答内容是一个字一个字“蹦”出来的?这种流畅的流式交互(Streaming)不仅是用户体验的关键,更是现代 AI 对话产品的标配。
那么,如何在自己的项目中实现这一效果?答案是:Server-Sent Events(SSE)。
相比 WebSocket,SSE 凭借其轻量、基于标准 HTTP、天然支持流式传输等优势,已成为当前 AI 应用中服务端向客户端推送生成内容的首选方案。本文将带你从原理到实战,彻底掌握 SSE 在 AI 场景下的落地方法。
一、SSE 是什么?为什么它适合 AI 流式输出?
SSE(Server-Sent Events) 是一种基于 HTTP/1.1 的服务端推送技术。它通过维持一个长连接,让服务器能够持续向客户端发送文本数据,而无需客户端轮询。
核心特性
- 单向通信:仅支持 服务端 → 客户端 的数据流(完美契合 AI 回答场景);
- 纯文本协议:默认使用 UTF-8 编码,天然兼容 JSON;
- 自动重连:浏览器原生支持断线后自动重试;
- 基于标准 HTTP:无需协议升级,穿透代理、CDN、防火墙更简单。
✅ 正因如此,SSE 成为 AI 对话中“模型一边生成、前端一边渲染”的理想载体。
二、基础用法:原生 EventSource 的局限
浏览器提供了 EventSource 接口用于接收 SSE 流:
const es = new EventSource('/api/chat/stream');
es.onmessage = (event) => {
console.log('收到片段:', event.data);
};
es.onopen = () => console.log('连接建立');
es.onerror = (err) => console.error('连接出错', err);
但问题来了:
- 仅支持 GET 请求:无法携带复杂的用户输入、上下文或鉴权信息;
- 无法自定义 Header:比如传递
Authorization: Bearer xxx几乎不可能; - 参数只能拼在 URL 中:既不安全,也不符合 RESTful 设计。
在 AI 场景中,用户提问通常包含多轮上下文、敏感 token 和复杂结构,必须通过 POST + JSON 传输。此时,原生 EventSource 就显得力不从心。
三、企业级方案:用 fetch-event-source 突破限制
微软开源的 @microsoft/fetch-event-source 库,正是为解决上述痛点而生。
它的优势包括:
- 支持 POST、PUT 等任意 HTTP 方法;
- 可自由设置 请求头(Headers),轻松集成 JWT 鉴权;
- 提供细粒度的 错误处理、重试策略与超时控制;
- 基于标准
fetchAPI,兼容现代前端框架。
实战封装示例(TypeScript)
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { ElMessage } from 'element-plus';
let abortController: AbortController;
/**
* 发起 SSE 流式请求
*/
export const requestStream = (
url: string,
headers: Record<string, string>,
data: unknown,
onChunk: (chunk: string) => void
) => {
abortController = new AbortController();
fetchEventSource(url, {
method: 'POST',
signal: abortController.signal,
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify(data),
openWhenHidden: true, // 页面后台时仍保持连接
onopen(response) {
console.log('SSE 连接已建立');
},
onmessage(event) {
if (event.data) {
onChunk(event.data);
}
},
onclose() {
console.log('SSE 连接正常关闭');
},
onerror(error) {
ElMessage.error('流式请求失败');
throw error; // 抛出错误以触发重试或终止
}
});
};
/**
* 主动中断请求(谨慎使用!)
*/
export const stopStream = () => {
if (abortController) {
abortController.abort();
abortController = new AbortController();
}
};
⚠️ 重要提醒:前端“停止” ≠ 后端“停止”
调用 abort() 仅会中断前端接收,后端仍在继续生成。这会导致:
- 历史消息缺失;
- Token 计费不一致;
- 多端同步异常。
最佳实践:提供一个 /stop 接口,由前端调用通知后端终止生成,确保状态一致。
四、关键排坑:Nginx 代理必须关闭缓冲!
即使前后端代码正确,你可能仍会发现内容“整段弹出”,而非逐字流式显示。罪魁祸首往往是 Nginx 的响应缓冲机制。
Nginx 默认会缓存后端响应,攒够一定量才转发给客户端,破坏了流式体验。
正确配置如下:
location ^~/api/v1/chat/ {
proxy_pass http://your_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s; # 防止长连接被超时断开
# 关键!关闭代理缓冲
proxy_buffering off;
# 启用分块传输(Chunked Transfer)
chunked_transfer_encoding on;
}
🔧
proxy_buffering off是实现“打字机效果”的必要条件。
五、SSE vs WebSocket:为何 AI 场景偏爱 SSE?
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(Server → Client) | 全双工(双向) |
| 协议基础 | 标准 HTTP/1.1 | 独立 WS 协议(需 Upgrade) |
| 数据格式 | 文本(UTF-8) | 文本或二进制 |
| 连接开销 | 轻量,复用 HTTP 连接 | 需握手、维护独立连接 |
| 穿透性 | 天然兼容 CDN、网关、负载均衡 | 部分中间件需特殊配置 |
| 适用场景 | 服务端持续推送(如 AI 生成) | 实时双向交互(如聊天室、游戏) |
在 AI 对话中,前端只发一次请求,后端持续返回——这正是 SSE 的“舒适区”。而 WebSocket 的双向能力在此属于“过度设计”,徒增复杂度。
结语:流式体验,细节决定成败
实现“打字机式”AI 回答,看似只是前端加个流式渲染,实则涉及:
- 后端分块输出(
Transfer-Encoding: chunked); - 前端使用合适的 SSE 客户端;
- 网关/Nginx 关闭缓冲;
- 状态一致性保障(避免前端强行中断)。
SSE 虽简单,却承载着现代 AI 产品最核心的交互体验。掌握它,你就掌握了通往专业级 AI 应用的第一把钥匙。
💡 小建议:在调试时,可用
curl -N http://your-api/stream查看原始流式响应,快速定位是后端未分块,还是前端/Nginx 缓冲问题。
让每一字都“呼吸”起来,才是真正的 AI 对话体验。
共同学习,写下你的评论
评论加载中...
作者其他优质文章