FastAPI SSE端点关闭客户端后sse-starlette生成器卡住问题
解决FastAPI SSE客户端断开后生成器阻塞的问题
问题根源
FastAPI的SSE生成器无法主动感知客户端断开,若生成器仅阻塞在asyncio.sleep()步骤,没有向客户端发送数据的操作,就无法触发连接断开的异常检测,导致生成器一直阻塞。
具体解决方案
1. 添加心跳检测并捕获发送异常
定期发送心跳消息,利用发送操作触发连接断开的异常,从而退出生成器:
from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse import asyncio app = FastAPI() async def sse_generator(request: Request): while True: try: # 发送心跳事件(空内容或标识心跳的文本) yield "event: heartbeat\n\n" await asyncio.sleep(5) except Exception: # 客户端断开时发送操作会抛出异常,直接退出循环 print("客户端已断开,生成器退出") break @app.get("/sse") async def sse_endpoint(request: Request): return StreamingResponse( sse_generator(request), media_type="text/event-stream" )
2. 主动检查连接状态
利用FastAPI提供的request.is_disconnected()方法,在每次循环时主动检测连接状态:
async def sse_generator(request: Request): try: while True: # 先检查连接是否存活 if await request.is_disconnected(): break yield "data: 实时更新内容\n\n" await asyncio.sleep(5) finally: print("生成器已退出")
注意:部分场景下连接断开后is_disconnected()可能不会立即返回True,配合发送操作检测更可靠。
3. 避免长时间阻塞sleep
如果需要长时间休眠,用asyncio.wait()同时等待sleep完成和连接断开事件,避免阻塞:
async def sse_generator(request: Request): while True: # 创建sleep任务,同时监听连接状态 sleep_task = asyncio.create_task(asyncio.sleep(30)) disconnect_check = asyncio.create_task(request.is_disconnected()) done, pending = await asyncio.wait( [sleep_task, disconnect_check], return_when=asyncio.FIRST_COMPLETED ) # 若连接已断开,清理任务并退出 if await request.is_disconnected(): for task in pending: task.cancel() print("客户端断开,退出生成器") break # 连接正常,发送数据并清理任务 yield "data: 延迟更新内容\n\n" for task in pending: task.cancel()
环境适配注意事项
- 确保FastAPI版本≥0.68.0,Python 3.8对asyncio的支持完全适配Ubuntu 20.04环境
- 测试时直接关闭客户端标签/进程,避免仅刷新页面(部分浏览器会复用连接)
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

