一、整体架构
- 前端通过
wss://<domain>/ws/chat连接后端 - 后端使用 FastAPI + Starlette WebSocket
- 部署方式:Docker Compose(含 Nginx 反向代理)
二、关键实现步骤
1. WebSocket 路由必须直接绑定到主应用
python
编辑
# ❌ 错误:通过 APIRouter + include_router 注册(无效!)
router = APIRouter()
@router.websocket("/ws/chat")
async def ws(websocket: WebSocket): ...
app.include_router(router) # ← WebSocket 路由不会被加载!
# ✅ 正确:使用 app.add_websocket_route 或 @app.websocket
from fastapi import FastAPI
app = FastAPI()
@app.websocket("/ws/chat")
async def chat_websocket(websocket: WebSocket):
await websocket.accept()
# ... 业务逻辑
💡 原因:
APIRouter仅支持 HTTP 路由,不支持 WebSocket 路由注册。
2. 必须调用 await websocket.accept()
python
编辑
# ❌ 错误:手动发送 accept 消息(状态不同步)
await websocket._send({"type": "websocket.accept"})
# ✅ 正确:使用官方方法
await websocket.accept()
⚠️ 否则会触发:
RuntimeError: WebSocket is not connected. Need to call "accept" first.- 后续
send_text()/receive_json()全部失败
3. 异常处理需谨慎
在 except 块中调用 send_text() 时,若连接已断开,会抛出新异常。建议:
python
编辑
try:
await websocket.send_text("...")
except (RuntimeError, WebSocketDisconnect):
pass # 客户端已断开,安全忽略
4. Nginx 配置必须支持 WebSocket 协议升级
nginx
编辑
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
🔑 关键头:
Upgrade和Connection: upgrade否则 Nginx 会当作普通 HTTP 请求处理,导致 403 或连接失败。
5. Docker 环境调试技巧
-
使用
docker-compose logs -f <service>实时查看日志 -
启动前验证 Python 语法:bash 编辑
python -m py_compile app/routers/socket.py -
清理环境(避免旧镜像干扰):bash 编辑
docker system prune -af --volumes
三、典型错误与解决方案速查
表格
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
连接返回 403 Forbidden |
WebSocket 路由未注册(用了 include_router) |
改用 @app.websocket 或 app.add_websocket_route |
RuntimeError: WebSocket is not connected |
未调用 websocket.accept() |
替换 _send(...) 为 await websocket.accept() |
Expected 'websocket.accept' but got 'websocket.send' |
在 accept 前 send,或异常中 send 失败 | 确保先 accept;异常中加 try-except |
| 连接立即断开 | Nginx 未配置 Upgrade 头 |
检查 Nginx 的 proxy_set_header Upgrade |
| 容器启动失败(IndentationError) | Python 缩进混用空格/TAB | 统一用 4 空格,编辑器开启"显示空白字符" |
四、推荐项目结构(模块化)
text
编辑
/app
├── main.py # 主应用入口(注册 WebSocket)
└── routers/
└── socket.py # 仅定义 async 函数,不包含 router
main.py 中:
python
编辑
from app.routers.socket import chat_websocket
app.add_websocket_route("/ws/chat", chat_websocket)
五、测试建议
-
本地用 wscat 测试:bash
编辑
wscat -c ws://localhost:8000/ws/chat -
生产环境用浏览器 DevTools 查看 Network → WS 面板