Skip to content

流式响应

流式响应知识

通用流式响应

通用流式响应(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,实现更简单,适合只读型实时更新场景(如通知、股票行情、日志流等)。

工作原理

  • 客户端通过 EventSource API 向服务器发起一个 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 是更轻量的选择;若需分段传输大量数据,流式响应更合适。
特性SSEWebSocket
协议基于 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")

image-20260202094418102

前端

在浏览器中访问 /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")

image-20260202095815810

前端

前端实现,参考上一节。