Nginx+uWSGI+Django环境WebSocket请求404问题排查求助
从你的描述来看,问题大概率出在组件配置冲突、Nginx转发规则缺失或者Django WebSocket路由实现不匹配这几个方面,咱们一步步拆解:
1. 先理清:你混用了两种不兼容的Django WebSocket实现
你的配置里同时出现了两个完全独立的WebSocket方案,这是核心矛盾:
- uwsgi.ini中配置了
WEBSOCKET_FACTORY_CLASS="dwebsocket.backends.uwsgi.factory.uWsgiWebSocketFactory"(属于dwebsocket库的配置) - Django settings里又用了
ProtocolTypeRouter({'websocket':AuthMiddlewareStack(URLRouter(alarm.routing.channel_routing))})(属于Django Channels的配置)
这两个库的路由机制完全不同,同时启用会导致WebSocket请求找不到对应的处理逻辑,直接返回404。你必须二选一:
- 若继续用dwebsocket:删掉Channels相关的
ProtocolTypeRouter配置,改用dwebsocket的路由方式(比如在urls.py里给/alarm配置带@require_websocket装饰器的视图) - 若使用Channels:删掉uwsgi.ini里的dwebsocket工厂类配置,改用Channels推荐的部署方式(比如用daphne作为WebSocket入口,或配置uWSGI支持Channels的ASGI协议)
2. 修正uWSGI的启动参数与配置匹配性
你当前的启动命令uwsgi --init uwsgi.ini --http_websockets和配置存在冲突:
http = :8008是让uWSGI直接作为HTTP服务器监听8008端口,--http_websockets是给这个模式开启WebSocket支持的socket = :10002是让uWSGI通过TCP socket和Nginx通信,这个模式下需要用--websockets参数(而非--http_websockets)开启WebSocket支持
因为你的Nginx是监听10001并转发到uWSGI的10002端口,正确做法是:
- 删掉uwsgi.ini里的
http = :8008(不需要uWSGI直接对外提供HTTP服务) - 启动命令改为
uwsgi --ini uwsgi.ini --websockets(给socket模式开启WebSocket支持)
3. 补全Nginx的WebSocket转发规则
即使uWSGI配置正确,Nginx如果没处理好WebSocket的握手请求,也会导致失败。针对/alarm路径,你的Nginx location配置必须包含WebSocket相关头转发:
server { listen 10001; server_name your_host; # 处理HTTP请求(比如/events路径) location /events { include uwsgi_params; uwsgi_pass 127.0.0.1:10002; } # 处理WebSocket请求(/alarm路径) location /alarm { include uwsgi_params; uwsgi_pass 127.0.0.1:10002; # 必须添加这两个头支持WebSocket握手 uwsgi_param UWSGI_SCHEME $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
注意:使用uwsgi_pass时,要确保uWSGI的uwsgi_params文件包含必要头,同时添加UWSGI_SCHEME让uWSGI识别请求类型。
4. 验证Django路由的正确性
若选择dwebsocket方案
在urls.py里明确配置/alarm的WebSocket视图:
from django.urls import path from . import views urlpatterns = [ path('events/', views.events_view), path('alarm/', views.alarm_websocket_view), ]
对应的视图使用dwebsocket装饰器:
from dwebsocket.decorators import require_websocket @require_websocket def alarm_websocket_view(request): # 处理WebSocket逻辑示例 while True: message = request.websocket.wait() if message is None: break request.websocket.send(message)
若选择Channels方案
确保alarm.routing.channel_routing正确映射/alarm路径到消费者:
# alarm/routing.py from django.urls import re_path from . import consumers websocket_urlpatterns = [ re_path(r'^alarm/$', consumers.AlarmConsumer.as_asgi()), ]
在settings.py里指定ASGI应用入口:
ASGI_APPLICATION = 'your_project.asgi.application'
同时在uwsgi.ini里添加ASGI支持配置:
asgi = your_project.asgi:application http-websockets = true
最后一步:重启服务测试
修改配置后按顺序重启:
- 停止uWSGI进程:
uwsgi --stop uwsgi.pid(若配置了pid文件) - 重启Nginx:
nginx -s reload - 重新启动uWSGI:
uwsgi --ini uwsgi.ini --websockets(对应dwebsocket)或uwsgi --ini uwsgi.ini(对应Channels)
再用ws.create_connection("ws://127.0.0.1:10001/alarm")测试,应该就能正常握手了。
内容的提问来源于stack exchange,提问作者LittleCabbage

