You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Heroku部署Django Channels时WebSocket连接404失败如何解决

Heroku部署Django Channels WebSocket 404问题排查

问题现象

在Heroku平台部署集成Django Channels的Django项目后,从本地前端地址http://localhost:3000/向服务端发起WebSocket连接时失败,错误截图如下:
WebSocket连接失败错误截图

从运行日志可以看到,普通HTTP接口可正常返回200状态码,Heroku Redis插件运行状态正常,但所有WebSocket请求路径均返回404 Not Found。


相关代码与配置信息

前端WebSocket连接逻辑

useEffect(() => {
  Socket = new WebSocket(`${WEBSOCKET_URL}/room/${roomName}/${myUserName}/`);

  Socket.onmessage = ({ data }) => {
    let res = JSON.parse(data);

    if (!res.error && res["data-type"] === "begin-game") {
      const { gameId } = res;
      Socket.close();
      navigate(`/game/${gameId}/`, {
        state: { gameId, data: res.data, myUserName },
      });
    } else if (!res.error) setUsersOnRoom({ ...res.data });
  };
}, []);

其中WebSocket服务地址配置为:

export const WEBSOCKET_URL = "wss://ludo-thegameforlegends.herokuapp.com/ws";

后端Django配置

asgi.py配置

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter,URLRouter
from channels.auth import AuthMiddlewareStack
from main.routing import websocket_urlpatterns as room_urlpatters
from gameManager.routing import websocket_urlpatterns as gameManager_urlpatterns
from channels.security.websocket import AllowedHostsOriginValidator


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ludo.settings')

websocket_urlpatterns = room_urlpatters + gameManager_urlpatterns


application = ProtocolTypeRouter({
    'http':get_asgi_application(),
    'websocket':AllowedHostsOriginValidator(AuthMiddlewareStack(URLRouter(websocket_urlpatterns))),
})

Channel Layers配置

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [(os.environ.get('REDIS_URL'), 6379)],
        },
    },
}

WebSocket路由配置

from django.urls import path
from .consumers import room_consumer

websocket_urlpatterns = [
    path('ws/room/<str:roomName>/<str:userName>/',room_consumer.as_asgi()),
]

WebSocket消费者代码

import json
from .models import Room
from gameManager.models import GameInstance
from asgiref.sync import async_to_sync
from .serializers import RoomSerializer
from channels.generic.websocket import WebsocketConsumer
from .utils import changeUserInDatabase,setDisconnectingUserToNoneInDatabase

class room_consumer(WebsocketConsumer):
    def connect(self):
        self.accept()
        self.room_group_name = self.scope['url_route']['kwargs']['roomName']
        self.user_name = self.scope['url_route']['kwargs']['userName']

        async_to_sync(self.channel_layer.group_add)(self.room_group_name,self.channel_name) 

        room = Room.objects.get(name=self.room_group_name)
        if not room.nameExists(self.user_name):
            room,userColor = changeUserInDatabase(self.room_group_name,self.user_name)
            self.color = userColor

            user_list = RoomSerializer(room).data
            async_to_sync(self.channel_layer.group_send)(self.room_group_name,{'type':'broadcast','error':None,'data-type':'user-joined-or-disconnected','data':user_list}) 

Heroku运行日志

2022-06-25T06:10:09.516891+00:00 app[web.1]: 10.1.56.26 - - [25/Jun/2022:06:10:09 +0000] "POST /api/create-or-verify/ HTTP/1.1" 200 54 "http://localhost:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"
2022-06-25T06:10:09.515137+00:00 heroku[router]: at=info method=POST path="/api/create-or-verify/" host=ludo-thegameforlegends.herokuapp.com request_id=7d47025d-6589-407b-81a1-094184123a43 fwd="27.34.26.234" dyno=web.1 connect=0ms service=5ms status=200 bytes=410 protocol=https
2022-06-25T06:10:11.282950+00:00 app[web.1]: Not Found: /ws/room/asdf/ghhhjh/
2022-06-25T06:10:11.283285+00:00 app[web.1]: 10.1.30.202 - - [25/Jun/2022:06:10:11 +0000] "GET /ws/room/asdf/ghhhjh/ HTTP/1.1" 404 2262 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"
2022-06-25T06:10:11.284165+00:00 heroku[router]: at=info method=GET path="/ws/room/asdf/ghhhjh/" host=ludo-thegameforlegends.herokuapp.com request_id=a453dbc3-f64d-4598-8123-256ff300ab4d fwd="27.34.26.234" dyno=web.1 connect=0ms service=4ms status=404 bytes=2582 protocol=https
2022-06-25T06:10:12.129139+00:00 heroku[router]: at=info method=GET path="/ws/room/asdf/ghhhjh/" host=ludo-thegameforlegends.herokuapp.com request_id=7b668aa1-e9fa-45b6-ab76-601ea563d62c fwd="27.34.26.234" dyno=web.1 connect=0ms service=6ms status=404 bytes=2582 protocol=https
2022-06-25T06:10:12.128284+00:00 app[web.1]: Not Found: /ws/room/asdf/ghhhjh/
2022-06-25T06:10:12.128987+00:00 app[web.1]: 10.1.16.221 - - [25/Jun/2022:06:10:12 +0000] "GET /ws/room/asdf/ghhhjh/ HTTP/1.1" 404 2262 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"
2022-06-25T06:07:06.000000+00:00 app[heroku-redis]: source=REDIS addon=redis-sinuous-30846 sample#active-connections=1 sample#load-avg-1m=0.25 sample#load-avg-5m=0.3 sample#load-avg-15m=0.36 sample#read-iops=0 sample#write-iops=0 sample#memory-total=15618680kB sample#memory-free=10552720kB sample#memory-cached=2820572kB sample#memory-redis=333744bytes sample#hit-rate=1 sample#evicted-keys=0

问题根因与修复步骤

日志里的404是Django HTTP路由层抛出的,说明WebSocket请求根本没走到Channels的路由处理逻辑,核心是Heroku启动应用的方式不对:默认用的gunicorn是WSGI服务器,不支持WebSocket协议,所有ws路径的请求被当成普通HTTP请求匹配Django的urlpatterns,找不到对应路由就返回404。按以下步骤修复即可:

  1. 修改启动配置,使用ASGI服务器启动应用
    在项目根目录创建/修改Procfile文件,替换默认的gunicorn启动命令为Channels官方推荐的daphne ASGI服务器:

    web: daphne ludo.asgi:application --port $PORT --bind 0.0.0.0
    

    同时把daphne添加到项目的requirements.txt依赖文件中,否则Heroku部署时会找不到daphne命令。

  2. 修正Redis Channel Layer配置
    Heroku提供的REDIS_URL环境变量已经包含认证信息、主机地址和端口,现有配置把URL和6379拆成元组传入会导致Redis连接失败,修改为:

    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [os.environ.get('REDIS_URL')],
            },
        },
    }
    
  3. 补充跨域访问配置
    本地前端http://localhost:3000属于跨域请求,需要在settings.py中添加允许的访问来源,避免被Origin校验拦截:

    ALLOWED_HOSTS = [
        "ludo-thegameforlegends.herokuapp.com",
        "localhost",
        "127.0.0.1"
    ]
    
    # 如果使用django-cors-headers,同步添加以下配置
    CORS_ALLOWED_ORIGINS = [
        "http://localhost:3000",
        "https://ludo-thegameforlegends.herokuapp.com"
    ]
    

所有配置修改完成后,提交代码重新推送到Heroku触发部署,WebSocket连接即可正常建立。


内容的提问来源于stack exchange,提问作者Sachin Bhattarai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 05:42:15