[Python] Django Channels WebSocket connects but immediately closes (Live chat app)

Discussão em 'Python' iniciado por Stack, Abril 26, 2025.

  1. Stack

    Stack Membro Participativo

    I am building a live streaming website where users can watch a live class and interact via WebSocket chat.

    I have integrated Django Channels, Redis, and HLS.js for video playback.

    The chat connects via a WebSocket to Django Channels.
    However, the issue is:

    • The WebSocket connects successfully, but immediately closes afterward.
    • The browser console shows multiple attempts to reconnect, but the problem persists.

    There is no clear error message except "WebSocket connection closed" in the JavaScript console.

    My Setup

    • Django 5.1.8
    • channels
    • channels-redis
    • Redis server is running (redis-cli ping returns PONG)
    • Development server (local machine, runserver + daphne for Channels)
    Code Snippets

    HTML/JS Side (live_stream.html)


    <script>
    // WebSocket Chat functionality
    const streamId = "UDOOSDIHOH49849"; // Your stream_id
    let chatSocket = null;
    let reconnectAttempts = 0;
    const maxReconnectAttempts = 5;

    // You can fetch real username from Django template if available
    const username = "Anonymous"; // You can dynamically change this later

    function connectWebSocket() {
    chatSocket = new WebSocket(
    'ws://' + window.location.host + '/ws/chat/' + streamId + '/'
    );

    chatSocket.onopen = function(e) {
    console.log('WebSocket connection established');
    reconnectAttempts = 0;
    };

    chatSocket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    const chatBox = document.getElementById('chatMessages');
    const newMessage = document.createElement('p');
    newMessage.innerHTML = `<strong>${data.username}:</strong> ${data.message}`;
    chatBox.appendChild(newMessage);
    chatBox.scrollTop = chatBox.scrollHeight;
    };

    chatSocket.onclose = function(e) {
    console.log('WebSocket connection closed');
    if (reconnectAttempts < maxReconnectAttempts) {
    console.log('Attempting to reconnect...');
    reconnectAttempts++;
    setTimeout(connectWebSocket, 3000);
    } else {
    console.error('Maximum reconnection attempts reached');
    }
    };

    chatSocket.onerror = function(e) {
    console.error('WebSocket error:', e);
    };
    }

    // Initial connection
    connectWebSocket();

    // Send button functionality
    document.getElementById('sendBtn').onclick = function() {
    const input = document.getElementById('chatInput');
    const message = input.value.trim();
    if (message !== '' && chatSocket && chatSocket.readyState === WebSocket.OPEN) {
    chatSocket.send(JSON.stringify({
    'message': message,
    'username': username // Send both message and username
    }));
    input.value = '';
    }
    };

    // Enter key support
    document.getElementById('chatInput').addEventListener('keypress', function(e) {
    if (e.key === 'Enter') {
    document.getElementById('sendBtn').click();
    }
    });

    </script>

    views.py


    from django.contrib.auth.decorators import login_required
    from django.shortcuts import render

    @login_required
    def watch_stream(request, stream_id="UDOOSDIHOH49849"):
    return render(request, 'video/live_stream.html', {
    'stream_id': stream_id,
    'user': request.user,
    })

    consumers.py


    # consumers.py
    import json
    from channels.generic.websocket import AsyncWebsocketConsumer
    from channels.db import database_sync_to_async

    class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
    from videoconference_app.models import LiveStream # Import here to avoid app loading issues
    try:
    self.stream_id = self.scope['url_route']['kwargs']['stream_id']
    self.user = self.scope['user']
    self.room_group_name = f'chat_{self.stream_id}'

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

    await self.accept()

    # Notify others about new user joining
    await self.channel_layer.group_send(
    self.room_group_name,
    {
    'type': 'chat_message',
    'message': f"{self.user.username if self.user.is_authenticated else 'Anonymous'} joined the chat!",
    'username': self.user.username if self.user.is_authenticated else 'Anonymous'
    }
    )

    except Exception as e:
    print(f"WebSocket connect error: {e}")
    await self.close()

    async def disconnect(self, close_code):
    try:
    await self.channel_layer.group_discard(
    self.room_group_name,
    self.channel_name
    )
    except Exception as e:
    print(f"WebSocket disconnect error: {e}")

    async def receive(self, text_data):
    try:
    text_data_json = json.loads(text_data)
    message = text_data_json.get('message', '')
    username = text_data_json.get('username', 'Anonymous')

    await self.channel_layer.group_send(
    self.room_group_name,
    {
    'type': 'chat_message',
    'message': message,
    'username': username
    }
    )
    except Exception as e:
    print(f"WebSocket receive error: {e}")

    async def chat_message(self, event):
    try:
    await self.send(text_data=json.dumps({
    'message': event['message'],
    'username': event.get('username', 'Anonymous')
    }))
    except Exception as e:
    print(f"WebSocket chat_message error: {e}")

    @database_sync_to_async
    def get_stream(self):
    from videoconference_app.models import LiveStream
    try:
    return LiveStream.objects.get(stream_key=self.stream_id)
    except LiveStream.DoesNotExist:
    return None


    routing.py


    from django.urls import re_path
    from videoconference_app import consumers

    websocket_urlpatterns = [
    re_path(r'ws/chat/(?P<stream_id>\w+)/$', consumers.ChatConsumer.as_asgi()),
    ]

    asgi.py


    import os
    from channels.routing import ProtocolTypeRouter, URLRouter
    from django.core.asgi import get_asgi_application
    from channels.auth import AuthMiddlewareStack
    import videoconference_app.routing

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

    application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
    URLRouter(
    videoconference_app.routing.websocket_urlpatterns
    )
    ),
    })

    settings.py (Channels + Redis)


    ASGI_APPLICATION = 'course.asgi.application'

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

    What I Have Already Tried

    • Verified that Redis server is active (PONG confirmed).
    • Checked that WebSocket URL is correct (ws:// since using http://localhost).
    • Confirmed that Daphne and Django run together (daphne course.asgi:application).
    • No visible errors in Django terminal except occasional WebSocket disconnect logs.
    • JavaScript client correctly retries but server immediately closes the connection.
    Additional Information

    • The stream page is protected via @login_required, so only authenticated users can access it.
    • The user object is available in the Django template.
    Request


    What could be causing the WebSocket to immediately close after connection?
    Am I missing anything in the consumer logic, ASGI setup, or Channels configuration?

    Any advice or debugging tips would be appreciated!

    Thank you in advance.

    Continue reading...

Compartilhe esta Página