Docker Compose环境下React通过/api代理连接FastAPI WebSocket的方法
问题:Docker+Nginx环境下,React与FastAPI的WebSocket连接失败
当前技术栈:Python FastAPI(后端)、ReactJS(前端),通过Docker Compose部署多后端副本,使用Nginx实现负载均衡。在建立WebSocket连接时遇到问题,核心需求是通过GUI的/api代理正确连接FastAPI的WebSocket端点。
现有配置文件
Docker Compose配置
version: "3.9" services: api: build: context: api ports: - 8000 load_balance: image: nginx:latest volumes: - ./dimer_api/nginx.conf:/etc/nginx/nginx.conf:rw depends_on: - api ports: - 8000:8000 gui: build: context: gui ports: - 80:80 depends_on: - load_balance
FastAPI代码
websockets = [] @router.websocket("/api/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() try: while True: message = await websocket.receive_text() data = {"status": "ok", "type": "message", "message": message} await websocket.send_text(json.dumps(data)) except WebSocketDisconnect: websockets.remove(message) def create_app(debug: bool = False) -> FastAPI: app = FastAPI(debug=debug) app.include_router(router) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) return app
React的WebSocket连接代码
useEffect(() => { const socket = new WebSocket("ws://localhost:8000/api/ws"); // 此处连接失败 socket.onopen = () => { console.log("WebSocket connection opened"); }; socket.onmessage = (event) => { const newData = JSON.parse(event.data); setData(newData); }; return () => { socket.close(); }; }, []);
Nginx负载均衡配置(load_balance服务)
events { worker_connections 1024; } http { server { listen 8000; location / { proxy_pass http://api:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } }
React应用的Nginx配置(gui服务)
server { listen 80; location /api { proxy_pass http://load_balance:8000; } location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
核心疑问
如何利用GUI的/api代理建立与FastAPI的WebSocket连接?正确的连接地址应该如何设置?
解决方案
1. 修改React的WebSocket连接地址
放弃硬编码localhost:8000,通过前端Nginx的/api代理转发,使用相对路径或自动适配当前域名的写法:
useEffect(() => { // 自动适配当前页面的协议(http对应ws,https对应wss)和域名 const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws`); socket.onopen = () => { console.log("WebSocket connection opened"); }; socket.onmessage = (event) => { const newData = JSON.parse(event.data); setData(newData); }; socket.onerror = (error) => { console.error("WebSocket error:", error); }; return () => { socket.close(); }; }, []);
更简洁的相对路径写法(浏览器会自动补全当前域名和协议):
const socket = new WebSocket("/api/ws");
2. 补充前端Nginx的/api代理WebSocket支持配置
当前前端Nginx的/api代理未配置WebSocket升级相关头,需补充:
server { listen 80; location /api { proxy_pass http://load_balance:8000; # 添加WebSocket支持的必要配置 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
3. 修复FastAPI代码中的WebSocket断开逻辑bug
原代码中websockets.remove(message)会报错,因为message是接收的文本而非连接对象,正确逻辑应维护连接对象列表:
websockets = [] @router.websocket("/api/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() # 将连接对象加入列表 websockets.append(websocket) try: while True: message = await websocket.receive_text() data = {"status": "ok", "type": "message", "message": message} await websocket.send_text(json.dumps(data)) except WebSocketDisconnect: # 移除当前连接对象 websockets.remove(websocket)
4. (可选)优化Nginx负载均衡配置(多后端场景)
若部署多个API副本,需在Nginx中配置上游服务器组实现负载均衡:
events { worker_connections 1024; } http { # 定义API上游服务器组,可添加多个后端节点 upstream api_servers { server api:8000; # server api2:8000; # server api3:8000; } server { listen 8000; location / { proxy_pass http://api_servers; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } }
内容的提问来源于stack exchange,提问作者kaiserm99
相关产品推荐
相关产品推荐

