flask的sse必须用生成器配合response返回,不能直接return字符串;需设mimetype='text/event-stream',每条消息以data:开头、双换行结尾,禁用缓存与content-length。

Flask 的 Response 必须配生成器,不能 return 普通字符串
直接在路由里 return "data: hello\n\n" 会失败——浏览器收到的是完整响应体,连接立刻关闭,根本不会触发 EventSource 的流式监听。SSE 依赖 HTTP 连接长期存活,而 Flask 默认把整个响应体算好长度、一次性发完,还带 Content-Length 头,这和 SSE 要求的“无界流”冲突。
正确做法是用生成器函数 yield 每条事件,再包进 Response(..., mimetype='text/event-stream'):
def event_stream():
for i in range(5):
yield f"data: {i}\n\n"
time.sleep(1)
<p>@app.route('/sse')
def sse():
return Response(event_stream(), mimetype='text/event-stream')</p>
注意三点:
-
mimetype必须写成'text/event-stream',content_type参数名虽可兼容,但易拼错 - 生成器里每条
yield后必须跟\n\n(两个换行),少一个就不是合法事件分隔符 - 别在生成器外加
try/except包整个yield循环——异常会中断流,且无法被前端捕获为 error 事件
浏览器收不到事件?先查响应头和格式是否合规
打开 DevTools → Network → 点开 /sse 请求,看 Preview 或 Response 标签页。如果看到纯文本(比如 data: 123 直接显示出来),说明格式或响应头出问题了。
必须同时满足:
- 响应头含
Content-Type: text/event-stream - 响应头禁用缓存:
Cache-Control: no-cache(否则 Chrome 可能缓存首条事件) - 响应体每条消息以
data:开头,结尾是\n\n,中间不能有多余空行或 HTML 标签 - 服务端没偷偷加
Content-Length(Flask 默认不加,但如果用了make_response或中间件可能引入)
常见翻车点:yield f"data:{json.dumps(obj)}\n\n" 缺了冒号后空格,某些旧版 Safari 会静默忽略;或者 yield "data: hi\r\n\r\n" 用了 \r\n,虽合法但不如 \n\n 兼容。
连接断开后自动重连失效?别在 onerror 里手动 new EventSource
浏览器原生的 EventSource 有内置重连机制:断开后默认等 3 秒重试,失败则指数退避。但很多人在前端写成:
es.addEventListener('error', () => {
es = new EventSource('/sse'); // ❌ 雪崩风险
});
这会导致错误时立刻新建连接,若服务端临时卡住,几十个并发请求瞬间打满后端。正确姿势是信任浏览器默认行为,只做有限干预:
- 服务端可通过
retry:字段控制重连间隔,例如yield "retry: 5000\n\n" - 前端只在必要时监听
error做 UI 提示,不主动重建实例 - 若需自定义重连逻辑(如降级轮询),应在
error回调里加防抖,且限制最大重试次数
多客户端并发时内存暴涨?生成器别闭包大对象
生成器函数若引用了全局列表、数据库连接或大字典,每个客户端连接都会持有一份引用,连接数一多就 OOM。典型错误写法:
cache = load_huge_data() # 全局加载一次
<p>def event_stream():
for item in cache: # 每个连接都遍历全量数据
yield f"data: {item}\n\n"</p>
应改为按需拉取、流式处理:
- 用数据库游标(
cursor.fetchmany())代替fetchall() - 把大文件分块读取:
for chunk in iter(lambda: f.read(8192), b'') - 避免在生成器作用域内初始化耗资源对象(如
requests.Session)
真正难的不是写通第一条 data:,而是让几百个长连接稳定跑一天不泄漏内存、不堆积未发送事件——这取决于你如何设计生成器的数据源和生命周期。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











