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
相关产品推荐
相关产品推荐

