pythondjangowebsocketdjango-channelsasgi

How can I fix the error of not being able to establish a connection to a Django Channels server with WebSockets using Python and ASGI?


Django channels & websockets : can’t establish a connection to the server.

I am trying to do a real-time drawing app using django channels, websockets & p5.

The only problem I've got is : Firefox can’t establish a connection to the server at ws://XXX.XXX.XXX.XXX:8090/ws/room/1/.

What I've done :

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");
})

Solution

  • The problem was in routing.py :

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

    the correct version is :

    websocket_urlpatterns = [
        path('ws/room/<room_code>/', DrawConsumer.as_asgi()),
    ]