前言
在开发 AI 对话产品(如 ChatGPT、豆包)时,如何实现那种"一个字一个字蹦出来"的流畅打字机效果?SSE(Server-Sent Events) 凭借其轻量级、基于 HTTP 的特性,成为了目前流式输出的主流方案。本文将从基础概念到企业级实战,带你彻底掌握 SSE。
一、 核心概念:什么是 SSE?
SSE (Server-Sent Events) 是一种基于 HTTP 协议的服务端推送技术。它利用 HTTP 的长连接特性,在客户端与服务器之间建立一条持久化通道,服务器通过这条通道实现数据的实时单向推送。一个基于SSE的聊天返回接口如下:

1. 核心特点
- 单向通信:仅支持服务器 → 客户端的数据推送。
- 数据格式:原生仅支持文本(默认 UTF-8 编码)。
- 自动重连:浏览器原生支持断线自动重连。
- 传统限制 :原生
EventSource仅支持 GET 方法,无法满足 AI 场景中复杂的 POST 参数需求。
二、 基础用法:原生 EventSource
EventSource为浏览器提供的原生接口,适用于简单的单向推送场景。具体使用如下:
json
// 1. 创建实例连接接口
const eventSource = new EventSource('http://localhost:3000/sse');
// 2. 监听消息
eventSource.onmessage = (event) => {
console.log('收到服务器数据:', event.data);
};
// 3. 监听自定义事件 (需与后端约定事件名)
eventSource.addEventListener('custom-event', (event) => {
console.log('自定义事件消息:', event.data);
});
// 4. 状态监听
eventSource.onopen = () => console.log('SSE 连接已建立');
eventSource.onerror = (err) => console.error('SSE 出错:', err);
// 5. 主动关闭
// eventSource.close();
注意事项:原生
EventSource仅支持 GET 方法,咱不能在向后端发送聊天内容时总是将参数拼在地址里面吧
三、 企业级实战:突破限制的 Fetch 方案
在 AI 场景下,用户的问题和上下文通常需要通过 POST 传输,此时原生 EventSource 就显得力不从心。推荐使用微软开源的 @microsoft/fetch-event-source 库。
1. 该方案的优势
- 支持 POST 及其他所有 HTTP 方法。
- 支持自定义请求头(Headers) (方便携带 Token 等鉴权信息)。
- 更好的错误重试策略和超时配置。
2.代码封装
以下是我在不同项目中调用流式接口时封装的请求函数:
typeScripyt
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { ElMessage } from 'element-plus'
let controller //用于中止请求
//请求流式接口数据
export const requestStream = (url, headers, data, handleMessage) => {
controller = new AbortController()
fetchEventSource(url, {
method: 'POST',
signal: controller.signal,
headers: headers,
body: JSON.stringify(data),
openWhenHidden: true, // 允许在页面隐藏时继续发送请求
async onopen(response) {
//建立连接的回调
console.log('建立连接的回调')
},
//接收一次数据段时回调,因为是流式返回,所以这个回调会被调用多次
onmessage(msg) {
handleMessage(msg)
},
//正常结束的回调
onclose() {
controller.abort() //关闭连接
},
//连接出现异常回调
onerror(err) {
ElMessage({ message: err, type: 'error' })
// 必须抛出错误才会停止
throw err
},
})
}
// 停止生成数据
export const stopRequest = () => {
if (controller) {
controller.abort()
controller = new AbortController()
}
}
注意事项:如果想前端主动停止流式输出的话,可以通过AbortController配置(不过在AI对话场景中不建议这样使用,因为这个方案为前端主动停止接收数据,而后端实际上是无感知的,在查看历史消息时会造成数据不一致!!!建议还是让后端创造一个停止接口
3、 关键排坑:Nginx 代理配置
在实际场景中我们发现前端代码没问题,但输出却是"一大块一大块"地蹦,没有打字机效果。这通常是因为 Nginx 开启了响应缓冲。
所以在配置 Nginx 代理时,必须显式关闭 proxy_buffering:
Nginx
location ^~/api/v1/chat/ {
proxy_pass http://backend_server;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_read_timeout 3600s; // 防止长连接超时断开
# 核心配置:关闭代理缓冲,实现流式即时传输
proxy_buffering off;
# 开启分块传输编码
chunked_transfer_encoding on;
}
四、SSE和WebSocket区别
关于WebSocket的概念与具体使用事项可以看这个文章:深度拆解 WebSocket
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(Server → Client) | 全双工(双向互发) |
| 协议基础 | 标准 HTTP/1.1 | 独立的 WS 协议(需握手升级) |
| 数据格式 | 纯文本/JSON | 二进制或文本 |
| 复杂度 | 轻量级,接入成本低 | 较重,逻辑复杂 |
五、 深度思考:为何 AI 对话多选择 SSE 而非 WebSocket?
- 功能契合度:AI 场景中前端只发一次请求(问题),后端持续输出(回答)。双向通信的 WebSocket 属于"功能过剩"。
- 兼容性与成本:SSE 基于标准 HTTP 协议,无需协议升级,天然支持流式输出,对 Nginx、网关的穿透性更好。