Django默认不支持原生SSE响应,因其HttpResponse会缓冲全部响应体并关闭连接,而SSE要求保持长连接、分块写入以data:开头的事件流;必须使用StreamingHttpResponse,显式设置text/event-stream类型与no-cache头,并避免Session等缓冲型中间件干扰。

为什么 Django 默认不支持原生 SSE 响应?
Django 的 HttpResponse 默认会缓冲响应体、关闭连接前写入全部内容,而 SSE 要求保持长连接、分块写入以 data: 开头的事件流。直接用 HttpResponse 返回字符串会等视图结束才发包,客户端收不到实时推送。
关键点在于:SSE 需要流式响应(StreamingHttpResponse),且必须禁用中间件干扰(尤其是 GZip、CSRF、Session 等可能缓冲或修改响应头的中间件)。
实操建议:
- 必须使用
StreamingHttpResponse,不能用HttpResponse或JsonResponse - 响应头需显式设置
Content-Type: text/event-stream和Cache-Control: no-cache - 每个事件块末尾必须有两个换行符(
\n\n),否则浏览器不会触发message事件 - 避免在视图中调用
request.session或request.user—— 它们会触发 Session 中间件,导致响应被缓冲
如何用 StreamingHttpResponse 实现基础 SSE 推送?
核心是构造一个可迭代对象(如生成器函数),每次 yield 一个符合 SSE 格式的字符串。注意每条消息要带 data: 前缀,可选 id:、event:、retry:。
立即学习“Python免费学习笔记(深入)”;
def sse_stream(request):
def event_generator():
while True:
# 模拟实时数据(实际中可从 Redis pub/sub、数据库轮询或信号接收)
yield f"data: {{\"msg\": \"tick\", \"ts\": {int(time.time())}}}\n\n"
time.sleep(1)
<pre class='brush:python;toolbar:false;'>response = StreamingHttpResponse(
event_generator(),
content_type="text/event-stream"
)
response["Cache-Control"] = "no-cache"
response["X-Accel-Buffering"] = "no" # 防 Nginx 缓冲
return response常见错误现象:
- 浏览器控制台报
Failed to load resource: net::ERR_INCOMPLETE_CHUNKED_ENCODING→ 通常是生成器抛异常或提前退出,没持续 yield - 收到数据但
onmessage不触发 → 检查是否漏了结尾的\n\n,或data:后少了空格 - 第一次连接成功,后续重连失败 → 没设
retry:,或服务端未处理客户端发送的Last-Event-ID头(如需断线续传)
如何在生产环境(Nginx + Gunicorn)中避免连接被中断?
Nginx 默认 60 秒超时、启用缓冲,Gunicorn 默认 worker timeout 为 30 秒,都会主动断开 SSE 连接。
配置要点:
- Nginx 配置中,该 location 必须加:
proxy_read_timeout 86400;proxy_buffering off;proxy_cache off;proxy_http_version 1.1;proxy_set_header Connection ''; - Gunicorn 启动时加参数:
--timeout 90 --keep-alive 5(--timeout要大于 Nginx 的proxy_read_timeout) - 确保 Django 的
USE_X_FORWARDED_HOST和SECURE_PROXY_SSL_HEADER正确设置,否则request.is_secure可能误判影响响应头
怎么安全地把后台事件(如 Celery 任务完成)推送给指定用户?
Django 视图本身无状态,不能直接“通知某个视图实例”。需借助外部通道(如 Redis Pub/Sub)解耦生产者与消费者。
典型流程:
- 前端建立 SSE 连接时,传
user_id或session_key作为查询参数(如/sse/?uid=123) - 后端视图启动时,用该标识订阅 Redis channel(如
user:123:sse) - Celery 任务完成后,执行
redis_client.publish("user:123:sse", json.dumps({...})) - 视图中的生成器用
redis_client.pubsub().subscribe(...)监听,并将收到的消息格式化为 SSE 字符串 yield 出去 - 注意:Redis pubsub 是单次消费,需用
pubsub.listen()阻塞迭代,且每次 yield 后要检查连接是否还活跃(防止客户端断开后继续占用资源)
客户端重连逻辑、服务端连接数限制、Redis 连接池复用这些细节,容易被忽略,但线上扛不住并发请求时最先暴露。


















