信号

Simon Willison 本周发布了 asgi-gzip 0.3,起因是在生产环境遇到了一个棘手的 Bug:SSE(text/event-stream )响应被中间件错误压缩,导致流式输出静默中断。修复其实早已在 Starlette( asgi-gzip 的上游库)中实现——但一个过期的 GitHub Actions 定时工作流已停止同步修复 到下游。手动触发一次工作流后,asgi-gzipdatasette-gzip 现在都能正确跳过对 text/event-stream 内容类型的压缩。

如果你正在运行任何带有 gzip 中间件的 Python ASGI 应用并使用流式响应,你现在可能就在 生产环境中存在这个 Bug 而不自知。

开发者的视角

SSE 是流式 LLM 输出的支柱。从 GPT-4、 Claude 或本地 Ollama 模型逐 token 转发到浏览器的响应都通过 text/event-stream 传输。如果你的 gzip 中间件静默破坏了这条流,用户看到的是损坏的输出或什么都没有——而 你的日志中没有任何错误。

这是成本/杠杆分析:SSE 流式传输是免费的基础设施。你只需写一个端点,浏览器自动 重连,就能获得实时用户体验而无需 WebSocket。独立开发者在这里的护城河是可靠性——交付不会 在生产环境中随机出问题的 AI 产品。配置错误的中间件层会悄悄摧毁这个护城河。

更 深刻的教训:从主项目剥离的库会与上游渐行渐远。asgi-gzip 有自动化同步机制,但仍然静默失败了。如果你正在使用小型工具中间件包,请 检查它们与上游的关系和最后发布日期。一个 18 个月没有发布新版本的包不一定是稳定的——它 可能只是被遗忘了。

杠杆计算:自己修复这个问题大约需要 2-4 小时来调试生产环境中的流 式中间件。升级一个包只需 30 秒。但你必须知道要去检查。这就是为什么关注像 Willison 这样公开发布修复的 开发者比阅读发布说明更有价值。

工具与栈

相关包

  • as gi-gzip 0.3 — 适用于任何 ASGI 应用(FastAPI、Starlette、Datasette)的 gzip 中间件。免费,MIT 许可。pip install asgi-gzip==0.3
  • datasette-gzip 0.2+ — 专为 Datasette 的封装。如果你在线 上运行 Datasette,也请升级这个。
  • Starlette 内置 GZip 中间件 — 如果你直接使用 Starl ette,这个已经打补丁了。请确认你使用的是 Starlette ≥0.27。

如何检查你的技术栈

# 检查你正在运行的版本 pip show asgi-gzip starlette datasette-gzip  # 升级 pip install --upgrade asgi-gzip datas ette-gzip

升级后如何验证 SSE 正常工作

# 最小 FastAPI SSE  端点测试 from fastapi import FastAPI from fastapi.responses import StreamingResponse from asgi_gzip import GZipMiddleware import asyncio  app = FastAPI() app.add_middleware(GZipMiddleware)  async def token_stream():      tokens = ["Hello", " world", " from", " streaming", " LLM"]     for token in tokens:         yield f"data: {token}\n\n"         await asyncio.sleep(0.1)  @app .get("/stream") async def stream():     return StreamingResponse(token_stream(), media_type="text/event-stream")
# 测试它——你应该看到原始 SSE  token,而不是 gzip 垃圾 curl -N http://localhost:8000/stream

如果你想完全绕过中间件的替代方案

  • C addy / nginx — 在反向代理层处理 gzip,配置为跳过 text/event-stream 内容类型。零 Python 代码风险。
  • Cloudflare Workers — 如果你通过 CF 代理,他们的压缩层默认正确处理 SSE。
  • 不使用 gzip — 对于纯 SSE/流式端点,gzip 压缩 几乎没有收益。流式负载每个 chunk 都很小。考虑选择性禁用 gzip。

本周 就上线

在一小时内构建一个正确处理 SSE 的流式 LLM 代理。

如果你一直推迟为 Python AI 应用添加实时流式传输,因为觉得 SSE 太脆弱,本 周就是你正确做事的机会。具体构建步骤:

  1. asgi-gzip 0.3 作为中间件启动一个 FastAPI 应用
  2. 添加一个 /chat/stream 端点,代理到 OpenAI 流式 API(或 Anthropic,或本地 Ollama)
  3. 返回 text/event-stream 响应,用上面的 curl 测试验证 gzip 不会破坏它们
  4. 部署 到 Fly.io 或 Railway——两者都支持免费/hobby 层的持久 SSE 连接
# 四条命令完成全栈 pip install fastapi uvicorn asgi-gzip openai # 编写你的流式端点(见上面的模式) u vicorn main:app --reload curl -N http://localhost:8000/chat/stream

Fly.io hobby 层对休眠 应用免费。Railway 提供每月 $5 免费额度。你的流式 AI 代理在低流量下几乎零成本运行。这就是杠杆—— 一次性修复基础设施,发布产品。