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

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.

Вернуться на верх