必须在 FastAPI WebSocket 路由开头 await websocket.accept(),否则连接因未完成握手而被拒绝;Channels 中 connect/receive/disconnect 均需 async def 且 self.send() 必须 await;Nginx 需配置 proxy_set_header Upgrade $http_upgrade 和 Connection "upgrade" 并启用 HTTP/1.1。

WebSocket 在 FastAPI 里怎么接住连接不立刻断开
FastAPI 的 WebSocket 路由必须显式调用 websocket.accept(),否则客户端会收到 403 或直接关闭。这不是可选步骤,是协议握手的硬性要求。
- 常见错误现象:
WebSocket closed with code 403或浏览器控制台报Failed to execute 'send' on 'WebSocket': Still in CONNECTING state - 必须在
async def路由函数开头就 awaitwebsocket.accept(),不能放在 if 判断后、不能漏掉、不能写成websocket.accept(少括号) - 如果要做鉴权,得在
accept()前完成(比如读 cookie / header),因为 accept 后无法再返回 HTTP 错误 -
websocket.close()要主动调用,不然连接可能滞留;但别在异常未捕获时跳过它,否则 ASGI server 可能卡住
Django Channels 的 consumers.py 为什么收不到消息
Channels 默认用 AsyncWebsocketConsumer,但很多人写了 receive() 却没加 async,或把 self.send() 当同步方法用——结果消息静默丢弃,无报错。
- 错误现象:前端发消息过去,服务端日志没打印,也没回传,
receive()像没执行 -
connect()、receive()、disconnect()全部必须是async def,哪怕你只做简单字符串处理 -
self.send()是异步方法,必须await self.send(...),写成self.send(...)不报错但无效 - 确保
ASGI_APPLICATION指向正确的 module.path(比如myproject.asgi.application),而不是旧的 WSGI 配置
FastAPI 和 Channels 都要处理心跳,但方式完全不同
浏览器 WebSocket 默认不发心跳,超时后代理/NAT 会单向断连。两边都得自己保活,但实现位置和逻辑不能混。
- FastAPI:通常在
while True:循环里用await asyncio.sleep(25)+await websocket.send_json({"type": "ping"}),注意别在 send 前忘了检查websocket.client_state == WebSocketState.CONNECTED - Channels:在
receive()里识别{"type": "ping"}并await self.send(...)回 pong;同时建议用self.channel_layer.group_send()向组内广播状态,避免轮询 - 别依赖
ping_interval这类浏览器私有 API,iOS Safari 和某些企业防火墙会拦截非标准 ping 帧
生产环境部署时,Nginx 怎么配才不吞掉 upgrade 头
Nginx 默认不转发 Connection: upgrade 和 Upgrade: websocket,导致 WebSocket 握手失败,降级成普通 HTTP 请求。
立即学习“Python免费学习笔记(深入)”;
- 错误现象:
WebSocket connection to 'wss://...' failed: Error during WebSocket handshake: Unexpected response code: 200 - 必须在 location 块里显式设置:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; -
proxy_http_version 1.1;必须开启,HTTP/1.0 不支持 upgrade 机制 - 如果用 HTTPS,确认证书链完整,且 Nginx 的
ssl_protocols包含 TLSv1.2+,老版本 TLS 可能导致 ws/wss 协商失败
真实线上问题往往卡在 Nginx 那一层,而不是代码逻辑——尤其是换了云厂商 SLB 或 CDN 后,它们默认不透传 upgrade 头,得单独开开关。










