外观
流式响应
流式响应知识
通用流式响应
通用流式响应(Generic Streaming Response)是指 Web 服务器在处理 HTTP 请求时,不等待整个响应内容完全生成,而是将数据分块逐步写入 HTTP 响应体并立即发送给客户端的一种技术模式。
- 传统 HTTP 响应:服务器生成完整响应体 → 一次性发送给客户端 → 客户端开始处理。
- 流式响应:服务器逐步生成内容 → 分块(chunk)发送 → 客户端边接收边处理(如渲染、显示、播放等)。
特点
- 无固定格式:可以是纯文本、JSON、HTML、CSV、二进制等任意格式
- 边生成边发送:服务端每产生一部分数据就立即推送,无需缓存完整结果
- 基于 HTTP 分块传输:使用 Transfer-Encoding: chunked,无需预知 Content-Length
- 单向通信:仅服务端 → 客户端(与 WebSocket 的双向不同)
- 通用性强:适用于任何需要“渐进式输出”的场景
应用场景
- 大文件下载(避免内存溢出)
- 实时日志输出(如 CI/CD 构建日志)
- AI 模型生成文本(如 ChatGPT 逐字输出)
- 视频/音频流(配合特定协议如 HLS、DASH)
技术基础:HTTP 分块传输编码(Chunked Transfer Encoding)
- HTTP/1.1 支持
Transfer-Encoding: chunked。 - 服务器无需提前知道内容总长度(即无需
Content-Length)。 - 每次写入一块数据(chunk),客户端立即可读。
- 最后以一个大小为 0 的 chunk 表示结束。
SSE流式响应
SSE(Server-Sent Events,服务器发送事件)是一种基于 HTTP 的单向流式通信技术,允许服务器主动向客户端(如浏览器)推送实时数据。与 WebSocket 不同,SSE 是单向的(服务器 → 客户端),并且建立在标准 HTTP/HTTPS 协议之上,因此更容易与现有 Web 架构集成。
特点
- 单向通信:仅服务器可以向客户端发送数据。
- 基于 HTTP:使用普通的 HTTP 连接,无需额外协议或端口。
- 自动重连机制:如果连接断开,浏览器会自动尝试重新连接。
- 文本格式传输:数据以 UTF-8 编码的文本形式发送,通常为 JSON。
- 简单轻量:相比 WebSocket,实现更简单,适合只读型实时更新场景(如通知、股票行情、日志流等)。
工作原理
- 客户端通过
EventSourceAPI 向服务器发起一个 HTTP 请求,并保持连接打开。 - 服务器响应时设置
Content-Type: text/event-stream。 - 服务器持续通过该连接发送格式化的文本事件(遵循 SSE 协议格式)。
- 客户端通过监听
message事件接收数据。
SSE数据格式
- SSE 使用特定的文本格式,每条消息由若干字段组成,以
\n分隔,消息之间用空行(\n\n)分隔: - 常用字段:
data::实际数据内容(可多行,最终拼接)。event::自定义事件类型(客户端可用addEventListener监听)。id::消息 ID,用于断线重连时指定从哪条开始(通过Last-Event-ID请求头)。retry::重连间隔(毫秒)。
shell
event: update
data: {"id": 1, "message": "Hello"}
id: 123
retry: 5000应用场景
- 实时通知(如新消息提醒)
- 股票价格、体育比分更新
- 日志监控、构建状态推送
- 服务端主动推送的只读数据流
- 不适合
- 需要双向通信(如聊天室)→ 应用 WebSocket
- 传输二进制数据 → SSE 只支持文本
不同类型流式响应对比
通用流式响应与SSE的对比
- SSE 是通用流式响应的一个“标准化子集”,专为事件推送设计;而通用流式响应更底层、更自由。
| 对比项 | 通用流式响应 | SSE(Server-Sent Events) |
|---|---|---|
| 协议规范 | 无,自由格式 | 有严格格式(data:、event: 等) |
| MIME 类型 | 任意(如 text/plain, application/json) | 必须为 text/event-stream |
| 浏览器 API | 需用 fetch + ReadableStream 手动解析 | 可直接用 EventSource 自动解析 |
| 自动重连 | ❌ 不支持 | ✅ 内置重连机制 |
| 事件类型 | ❌ 无事件概念 | ✅ 支持自定义事件名 |
| 适用场景 | 文件流、AI 生成、日志、大文件下载等 | 实时通知、状态推送等结构化事件流 |
SSE与WebSocket的对比
- 均可用于实时通信。若需双向通信,应选择 WebSocket;若仅需服务器单向推送,SSE 是更轻量的选择;若需分段传输大量数据,流式响应更合适。
| 特性 | SSE | WebSocket |
|---|---|---|
| 协议 | 基于 HTTP | 独立的 WebSocket 协议(ws/wss) |
| 通信方向 | 单向(服务器→客户端) | 双向(客户端↔服务器) |
| 连接开销 | 低(复用 HTTP 连接) | 高(需握手升级协议) |
| 重连机制 | 内置自动重连 | 需手动实现重连 |
| 数据格式 | 仅文本 | 支持文本和二进制 |
| 兼容性 | 浏览器支持稍弱(IE 不支持) | 主流浏览器均支持 |
通用流式实战示例
后端
在 FastAPI 中实现基础流式响应主要依赖Python 生成器(Generator) 产生分块数据,再通过StreamingResponse包装返回。
python
import asyncio
from fastapi.responses import StreamingResponse
# 异步生成器
async def number_streamer():
for i in range(1, 6):
yield f"当前计数:{i}\n".encode("utf-8")
await asyncio.sleep(1) # 异步延迟
# 通用流式响应接口
@app.get("/stream")
async def stream_number():
return StreamingResponse(number_streamer(), media_type="text/plain; charset=utf-8")
前端
在浏览器中访问 /stream接口时内容不是逐行出现,主要原因是浏览器默认不会主动解析和实时显示 HTTP 流式响应(分块传输)的每个数据块。要实现“逐行出现”效果,在前端使用 JavaScript 主动读取并处理这个流。
- 将下面内容保存为文件:
index.html,然后双击运行。
html
<!DOCTYPE html>
<html>
<body>
<div id="output"></div>
<script>
async function fetchStream() {
const response = await fetch("http://127.0.0.1:8000/stream"); // 请求您的接口
const reader = response.body.getReader(); // 获取流阅读器
const decoder = new TextDecoder("utf-8"); // 解码器
const outputDiv = document.getElementById("output");
while (true) {
const { done, value } = await reader.read(); // 读取一个数据块
if (done) break;
// 将二进制块解码为文本,并追加到页面上
const text = decoder.decode(value, { stream: true });
outputDiv.innerHTML += text; // 或使用 textContent 并处理换行
}
}
fetchStream();
</script>
</body>
</html>SSE流式响应实战示例
后端
注意:
- 使用
data: ... \n\n media_type="text/event-stream"
python
import asyncio
from fastapi.responses import StreamingResponse
async def sse_streamer():
"""异步生成器"""
for i in range(1, 11):
yield f"data: 当前计数:{i}\n\n"
await asyncio.sleep(1)
@app.get("/stream", summary="SSE流式响应")
async def stream_number():
return StreamingResponse(sse_streamer(), media_type="text/event-stream")
前端
前端实现,参考上一节。