直接用aiohttp.StreamResponse处理大数据流,因其避免内存溢出;需手动设content_type和status,用await response.write()写入bytes,分片写入并主动检测客户端断连。

直接用 aiohttp.StreamResponse 处理大数据流,不是为了“显得高级”,而是因为普通 Response 会把整个响应体加载进内存——几MB还行,几百MB或持续生成的 LLM 输出就会 OOM 或卡死。
StreamResponse 必须手动设置 content_type 和 status
它不像普通 Response 那样自动推断 MIME 类型或默认 200 状态码。漏设会导致前端解析失败或被浏览器拦截:
-
content_type必须显式指定,比如"text/event-stream"、"application/json"或"text/plain" -
status建议设为200,除非你有意返回错误码(如400) - 不调用
write_eof()就结束协程,连接可能被客户端静默关闭,后续 chunk 写入会抛RuntimeError: Cannot write to closing transport
写入流必须用 await response.write(),不能用 print 或 + 字符串拼接
StreamResponse 的底层是异步 socket 缓冲区,所有写入操作都是非阻塞的,且需等待底层传输就绪:
- 每次
await response.write(data)的data必须是bytes,传str会报TypeError - 大块数据建议分片写入(例如每 8192 字节),避免单次
write占用事件循环太久 - 不要在
write后立刻await asyncio.sleep(0)——这会人为引入延迟,反而降低吞吐
客户端断连时,response.writer.transport.is_closing() 为 True
LLM 流式输出或长轮询场景中,用户关页、刷新、网络中断很常见。aiohttp 不会自动抛异常,得主动检查:
立即学习“Python免费学习笔记(深入)”;
- 在循环写入前加判断:
if response.writer.transport.is_closing(): break - 写入后也可检查:
if response.writer.transport.get_extra_info("peername") is None:表示对端已失联 - 忽略断连继续写,会在某次
write时触发ConnectionResetError,但此时已浪费资源
和 StreamReader.content 搭配使用时,别混用同步/异步读取逻辑
服务端用 StreamResponse 发,客户端用 aiohttp.ClientResponse.content 接——这两者语义一致,但新手常在客户端误用同步方式:
- 错误写法:
response.content.read()(这是同步方法,会阻塞整个事件循环) - 正确写法:
await response.content.read(n)或async for chunk in response.content: - 若用
iter_chunked(65536),注意它返回的是bytes,不是str,JSON 流需手动.decode()
真正难的不是调用 write,而是在流式过程中做实时过滤、限速、超时熔断或跨请求状态共享——这些没法靠框架自动完成,得自己在协程里嵌套逻辑,稍不注意就让流“卡住”或“乱序”。


















