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

如何修复Python+ASGI环境下Django Channels的WebSocket连接失败问题

实时绘图应用WebSocket连接失败问题

我用Django Channels、WebSocket和p5开发实时绘图应用,但Firefox无法建立到ws://XXX.XXX.XXX.XXX:8090/ws/room/1/的连接,以下是相关配置代码:

settings.py

INSTALLED_APPS = [
    ...
    'channels',
]

ASGI_APPLICATION = 'DrawMatch.asgi.application'

CHANNEL_LAYERS = {
    'default': {
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
}

asgi.py

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DrawMatch.settings')
django_asgi_app = get_asgi_application()

application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                drawmatch_app.routing.websocket_urlpatterns
            )
        )
    ),
})

consumers.py

class DrawConsumer(AsyncJsonWebsocketConsumer):
    room_code: str = None
    room_group_name: str = None

    async def connect(self):
        self.room_code = self.scope['url_route']['kwargs']['room_code']
        self.room_group_name = f'room_{self.room_code}'
        print(f"room_code: {self.room_code}")
        print(f"room_group_name: {self.room_group_name}")

        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )
        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data: str = None, _: Any = None) -> None:
        data = json.loads(text_data)
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'draw',
                'data': data
            }
        )

    async def send_message(self, res):
        await self.send(text_data=json.dumps({
            "payload": res
        }))

routing.py

websocket_urlpatterns = [
    url(r'^ws/room/(?P<room_code>\w+)/$', DrawConsumer.as_asgi()),
]

views.py

def room(request, room_code):
    context = {
        'room_code': room_code
    }
    return render(request, 'room.html', context)

urls.py

urlpatterns = [
    path('', views.home),
    ...
    path('room/<room_code>/', views.room),
    path('predict', draw_guess.main),
]

room.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>DrawMatch - Room {{ room_code }}</title>
    <link rel="stylesheet" href="{% static 'styles/style.css' %}">
    <script src="{% static 'scripts/index.mjs' %}" type="module"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js"></script>
    <script>
        const csrftoken = '{{ csrf_token }}';
        const room_code = {{ room_code }};
        const connectionString = `ws://${window.location.host}/ws/room/{{room_code}}/`;
        const gameSocket = new WebSocket(connectionString);
    </script>
</head>
<body>
    <h1>Room {{ room_code }}</h1>
    <div class="drawings-container"></div>
</body>
</html>

index.js

const WIDTH = 500;
const HEIGHT = 500;
const STROKE_WEIGHT = 3;
const drawingsContainer = document.querySelector(".drawings-container");

console.log(gameSocket);

gameSocket.onmessage = (e) => {
    console.log(`Server: ${e.data}`)
    const data = JSON.parse(e.data);
    if (data.type === "draw") {
        const {
            canvas, x, y, px, py
        } = data.data;
        canvas.line(x, y, px, py);
    }
}

gameSocket.onopen = (e) => {
    console.log("Connected to websocket");
}

gameSocket.onclose = (e) => {
    console.log("Disconnected from websocket");
}

function setupCanvas(canvas, id) {
    console.log(connectionString);
    let timeout;
    let drawing = false;

    canvas.setup = () => {
        canvas.createCanvas(WIDTH, HEIGHT);
        canvas.strokeWeight(STROKE_WEIGHT);
        canvas.stroke("black");
        canvas.background("#FFFFFF");
        canvas.canvas.id = id;
        drawingsContainer.appendChild(canvas.canvas);
    }

    canvas.draw = () => {
        if (!drawing) return;
        canvas.line(canvas.mouseX, canvas.mouseY, canvas.pmouseX, canvas.pmouseY);

        if (timeout) return;
        timeout = setTimeout(async () => {
            const image = canvas.canvas.toDataURL();
            const response = await fetch("/predict", {
                method: "POST", body: JSON.stringify({
                    image
                }), headers: {
                    "X-CSRFToken": csrftoken, "Content-Type": "application/json"
                }
            })
            const data = await response.text();
            console.log(data);
            gameSocket.send(JSON.stringify({
                type: "draw", data: {
                    canvas: canvas.id, x: canvas.mouseX, y: canvas.mouseY, px: canvas.pmouseX, py: canvas.pmouseY
                }
            }));
            timeout = null;
        }, 200);
    }

    canvas.mousePressed = () => drawing = canvas.mouseX > 0 && canvas.mouseX <= WIDTH && canvas.mouseY > 0 && canvas.mouseY <= HEIGHT;

    canvas.mouseReleased = () => drawing = false
}

new p5(leftCanvas => {
    setupCanvas(leftCanvas, "leftCanvas");
})

new p5(rightCanvas => {
    setupCanvas(rightCanvas, "rightCanvas");
})

问题修复方案

  1. 修复模板JS语法错误
    room.html里的room_code变量如果是字符串类型会触发JS语法错误,修改为:
const room_code = '{{ room_code }}';
const connectionString = `ws://${window.location.host}/ws/room/${room_code}/`;
  1. 解决主机验证拦截问题
    如果前端访问IP和后端不一致,AllowedHostsOriginValidator会拦截WebSocket连接。可以先注释该验证进行测试:
# 修改asgi.py
application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AuthMiddlewareStack(
        URLRouter(
            drawmatch_app.routing.websocket_urlpatterns
        )
    ),
})

生产环境需在settings.py中添加正确的主机列表:

ALLOWED_HOSTS = ['XXX.XXX.XXX.XXX', 'localhost', '127.0.0.1']
  1. 修正消费者组移除方法
    Channels中移除组的正确方法是group_remove而非group_discard,修改consumers.py:
async def disconnect(self, close_code):
    await self.channel_layer.group_remove(
        self.room_group_name,
        self.channel_name
    )
  1. 添加消息处理方法
    消费者需要对应处理draw类型的消息才能转发给其他客户端,在consumers.py中新增:
async def draw(self, event):
    data = event['data']
    await self.send(text_data=json.dumps({
        "type": "draw",
        "data": data
    }))
  1. 使用ASGI服务器启动项目
    Django自带的runserver不支持WebSocket,必须用daphne启动:
daphne -b XXX.XXX.XXX.XXX -p 8090 DrawMatch.asgi:application

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 11:59:57