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

Django Channels聊天应用WebSocket连接失败及表单异常求助

Django Channels聊天应用:WebSocket连接失败&表单发GET请求问题排查

问题描述

我正在使用Django Channels、Redis和Daphne构建聊天应用。点击提交按钮发送消息时,页面发起了GET请求,但我已在HTML文件中编写JavaScript代码以阻止表单的默认行为。我不确定为什么WebSocket连接无法建立,以及为何会发送GET请求而非通过WebSocket通信。

我已确认Redis服务器正在运行,所有必需模块均已安装,甚至通过GPT排查过配置,但至今未解决问题。如何才能让WebSocket连接正常建立?

相关代码

consumer.py

import json

from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async

from django.apps import apps

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.chat_name = self.scope['url_route']['kwargs']['chat_name']
        await self.channel_layer.group_add(self.chat_name, self.channel_name)

        await self.accept()    
    
    @database_sync_to_async
    def save_message(self, text_data):
        Chat = apps.get_model('chat', 'Chat') 
        ChatMessage = apps.get_model('chat', 'ChatMessage')
        User = apps.get_model('auth', 'User')

        chat = Chat.objects.get(chat_name=text_data['chat_name'])
        sender = User.objects.get(username=text_data['sender'])
        ChatMessage.objects.create(
            chat=chat,
            body=text_data['message'],
            sender = sender,
        )
        
    async def receive(self, text_data):
        print('Received from frontend:', text_data)
        data_json = json.loads(text_data)
        await self.save_message(data_json)

        event = {"type": "send_chat_message", "message": data_json}
        
        await self.channel_layer.group_send(self.chat_name, event)

    async def send_chat_message(self, event):
        await self.send(text_data=json.dumps({"message": event["message"]})) 

chat.html

{% extends "base.html" %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat</title>
</head>
<body>
    {% block content %}
    <div class="container">
        <h2>Chat with {{ chat.members.all.0 }}</h2>
        <div class="card">
            <div class="card-body" id="message-container" style="height: 300px; overflow-y: scroll;">
                {% for message in chat_messages %}
                    <p><strong>{{ message.sender.username }}</strong>: {{ message.body }}</p>
                {% empty %}
                    <p>There are no meassages yet...</p>
                {% endfor %}
            </div>
        </div>
        <form action="" id="message-form" name='message-form'>
            <input name="message-input" id="message-input"></input>
            <button type="submit">Send</button>
        </form>
    </div>

    {% endblock %}


    <script>
            const chat_name = "{{ chat.chat_name }}"
            const socket = new WebSocket(`ws://127.0.0.1:8000/ws/chat/${chat_name}/`) // creatimg ws connection

            // sending the message to the socket
            const message_form = document.getElementById("message-form")
            message_form.addEventListener(              // Listener for submit button
                "submit", function (event) {
                    event.preventDefault()
                    let sent_message = document.getElementById("message-input").value // get the value of the input of the form
                    socket.send(
                        JSON.stringify({
                            message: sent_message,
                            chat_name: "{{ chat.chat_name }}",
                            sender: "{{ request.user.username }}"

                        })
                    )
                }
            )

            //Getting the message
            const chats_div = document.getElementById("message-container") // search for container where we will paste the message
            function scrollToBottom() {
                chats_div.scrollTop = chats_div.scrollHeight
            } //???

            //Processing of the message from the server 
            socket.addEventListener("message", function(e) {
                const data = JSON.parse(e.data);
                
                let sender = data.message.sender
                let content = data.message.message
                
                let msg_body = document.createElement("div")
                let msg_sender = document.createElement("div")
                
                msg_body.textContent = content
                msg_sender.textContent = sender
            
                let chats_div = document.getElementById("message-container")
                let msg = document.createElement("p")
                msg.innerHTML = `<strong>${msg_sender.textContent}</strong>: ${msg_body.textContent}`;
                
                chats_div.appendChild(msg);
                scrollToBottom()
            })
    </script>

</body>
</html> 

asgi.py

import os
import django  

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

from chat.routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'messanger.settings')
django.setup() 

application = ProtocolTypeRouter({
    "http": get_asgi_application(),  # Обычные HTTP-запросы
    "websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)), 
})

routing.py

from django.urls import re_path

from .consumers import ChatConsumer

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

views.py

from django.shortcuts import render

from .models import *

def chat_list(req):
    chats = req.user.chats.all()
    print(chats)
    return render(req, 'chat_list.html', {'chats': chats})

def chat(req, chat_name):
    chat = req.user.chats.get(chat_name=chat_name)
    chat_messages = ChatMessage.objects.filter(chat=chat)
    return render(req, 'chat.html', {'chat_messages': chat_messages, 'chat': chat})

models.py

from django.db import models
from django.contrib.auth.models import User


class Chat(models.Model):
    chat_name = models.CharField(max_length=50)
    members = models.ManyToManyField(User, related_name='chats')
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.chat_name
    

class ChatMessage(models.Model):
    body = models.TextField()
    chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name='chat')
    sender = models.ForeignKey(User, on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Message by @{self.sender} from the chat '{self.chat.chat_name}'"

解决方案

1. 修复表单默认行为失效问题

  • 确保JS正确绑定事件:当前JS代码直接运行时,可能DOM还未完全加载,导致无法找到message-form元素,监听事件绑定失败。把所有JS代码包裹在DOMContentLoaded事件中:
    document.addEventListener('DOMContentLoaded', function() {
        const chat_name = "{{ chat.chat_name }}"
        const socket = new WebSocket(`ws://${window.location.host}/ws/chat/${chat_name}/`)
    
        const message_form = document.getElementById("message-form")
        message_form.addEventListener("submit", function (event) {
            event.preventDefault()
            let sent_message = document.getElementById("message-input").value
            socket.send(
                JSON.stringify({
                    message: sent_message,
                    chat_name: "{{ chat.chat_name }}",
                    sender: "{{ request.user.username }}"
                })
            )
            // 清空输入框
            document.getElementById("message-input").value = ''
        })
    
        const chats_div = document.getElementById("message-container")
        function scrollToBottom() {
            chats_div.scrollTop = chats_div.scrollHeight
        }
    
        socket.addEventListener("message", function(e) {
            const data = JSON.parse(e.data);
            
            let sender = data.message.sender
            let content = data.message.message
            
            let msg = document.createElement("p")
            msg.innerHTML = `<strong>${sender}</strong>: ${content}`;
            
            chats_div.appendChild(msg);
            scrollToBottom()
        })
    });
    
  • 检查浏览器控制台报错:按F12打开控制台,查看是否有Uncaught TypeError: Cannot read properties of null这类错误,这说明元素未找到,需要调整JS加载时机。

2. 修复WebSocket连接失败问题

  • 用正确的ASGI服务器启动:必须使用Daphne或支持ASGI的服务器启动,不能用默认的runserver(除非加--asgi参数):
    # 方式1:用Daphne启动
    daphne messanger.asgi:application
    # 方式2:用runserver指定ASGI
    python manage.py runserver --asgi
    
  • 修正WebSocket路由匹配规则:当前正则\w+只能匹配字母、数字和下划线,如果chat_name包含其他字符(比如连字符),会导致路由不匹配。修改routing.py中的正则:
    websocket_urlpatterns = [
        re_path(r"ws/chat/(?P<chat_name>[^/]+)/$", ChatConsumer.as_asgi()),
    ]
    
  • 完善settings.py配置:确保添加了Channels和Redis相关配置:
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'channels',  # 添加这行
        'chat',      # 添加你的chat app
    ]
    
    # Channels配置
    ASGI_APPLICATION = "messanger.asgi.application"
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [("127.0.0.1", 6379)],  # 确保Redis端口正确
            },
        },
    }
    
  • 避免硬编码WebSocket地址:JS中用相对路径生成WebSocket URL,适配不同的运行环境:
    const socket = new WebSocket(`ws://${window.location.host}/ws/chat/${chat_name}/`)
    

3. 其他排查点

  • 检查用户登录状态:AuthMiddlewareStack会验证用户身份,如果未登录,WebSocket连接会被拒绝。确保访问聊天页面的用户已登录,或者在consumer中处理匿名情况。
  • 测试Redis连接:用redis-cli ping确认Redis正常响应,确保Channels能连接到Redis。
  • 排查consumer异常:在connect方法中添加日志或异常捕获,避免因chat_name不存在等问题导致连接失败:
    async def connect(self):
        try:
            self.chat_name = self.scope['url_route']['kwargs']['chat_name']
            await self.channel_layer.group_add(self.chat_name, self.channel_name)
            await self.accept()
        except Exception as e:
            print(f"Connection error: {e}")
            await self.close()
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:05:53