Django Channels WebSocket实时聊天应用遇CSRF验证失败及连接问题求助
Django Channels WebSocket聊天应用CSRF验证失败及连接问题
我是Django新手,正在搭建基于Channels WebSocket的实时聊天应用。不管是自己编写聊天页面还是直接复制教程代码,终端一直提示“CSRF验证失败,请求中止”和“禁止访问(缺少CSRF令牌)”。
已尝试的方案:
- 在表单上方添加
{% csrf_token %}——页面会刷新,消息未加入消息列表,但“禁止访问(缺少CSRF令牌)”提示消失; - 在chat/views.py中添加
@csrf_protect装饰器; - 使用教程指定的Channels 3.0.5版本;
- 重建数据库、删除并恢复所有迁移文件,无任何变化。
相关代码
chat/templates/chat/chat.html
{% extends 'core/base.html' %} {% block title %} {{ chat.name }} | {% endblock %} {% block content %} <div class="p-10 lg:p-20 text-center"> <h1 class="text-3xl lg:text-6xl text-white">{{ chat.name }}</h1> </div> <!-- div for actual messages--> <div class="lg:w-2/4 mt-6 mx-4 lg:mx-auto p-4 bg-white rounded-xl"> <div class="chat-messages space-y-3" id="chat-messages"> <div class="p-4 bg-gray-200 rounded-xl"> <p class="font-semibold">Username</p> <p>This is the message for the user</p> </div> <div class="p-4 bg-gray-200 rounded-xl"> <p class="font-semibold">Username</p> <p>This is the message for the user</p> </div> <div class="p-4 bg-gray-200 rounded-xl"> <p class="font-semibold">Username</p> <p>This is the message for the user</p> </div> <div class="p-4 bg-gray-200 rounded-xl"> <p class="font-semibold">Username</p> <p>This is the message for the user</p> </div> </div> </div> <!-- form for input and sending--> <div class="lg:w-2/4 mt-6 mx-4 lg:mx-auto p-4 bg-white rounded-xl"> <form method="post" action="." class="flex"> {% csrf_token %} <input type="text" name="content" class="flex-1 mr-3" placeholder="Your message..." id="chat-massage-input"> <button class="px-5 py-3 rounded-xl text-white bg-teal-600 hover:bg-teal-700" id="chat-message-submit">Submit</button> </form> </div> {% endblock %} {% block scripts %} {{ chat.slug | json_script:"json-chatname"}} {{ request.user.username | json_script:"json-username" }} <script> const chatName = JSON.parse(document.getElementById("json-chatname").textContent); const userName = JSON.parse(document.getElementById("json-username").textContent); const chatSocket = new WebSocket( "ws://" + window.location.host + "/ws/" + chatName + "/" ); chatSocket.onopen = function(e) { console.log("[open] Connection established"); }; chatSocket.onmessage = function(e){ console.log("on message"); const data = JSON.parse(e.data); if (data.message){ let html = '<div class="p-4 bg-gray-200 rounded-xl">' html += '<p class="font-semibold">' + data.username + '</p>' html += '<p>' + data.message + '</p> </div>' document.querySelector("#chat-messages").innerHTML += html; }else{ alert("The message is empty"); } }; chatSocket.onclose = function(e){ console.log("on close"); } document.querySelector("#chat-message-submit").onclick() = function(e){ e.preventDefault(); const messageInputDom = document.querySelector("#chat-message-input"); const message = messageInputDom.value; chatSocket.send(JSON.stringify({ "message" : message, "username" : userName, "chatname" : chatName, })) messageInputDom.value = ""; return false; } </script> {% endblock %}
chat/consumers.py
import json from channels.generic.websocket import AsyncWebsocketConsumer class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): self.chat_name = self.scope["url_route"]["kwargs"]["chat_name"] self.chat_group_name = "%s_group" % self.chat_name # add group to channel layers await self.channel_layer.group_add( self.chat_group_name, self.channel_name ) # connect to channel await self.accept() async def disconnect(self): await self.channel_layer.group_discard( self.chat_group_name, self.channel_name ) async def receive(self, text_data): data = json.loads(text_data) message = data["message"] username = data["username"] chat = data["chat"] await self.channel_layer.group_send( self.chat_group_name, { "type": "chat_message", "message":message, "username":username, "chat":chat, } ) async def chat_message(self, event): message = event["message"] username = event["username"] chat = event["chat"] await self.send(text_data=json.dumps({ "message": message, "username": username, "chat": chat, }))
chat/models.py
from django.db import models # Create your models here. class Chat(models.Model): name = models.CharField(max_length=255) slug = models.SlugField(unique=True)
chat/views.py
from django.shortcuts import render from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_protect # Create your views here. from .models import Chat @login_required @csrf_protect def chats(request): chats = Chat.objects.all return render(request, "chat/chats.html", {"chats" : chats}) @login_required @csrf_protect def chat(request, slug): chat = Chat.objects.get(slug=slug) return render(request, "chat/chat.html", {"chat" : chat})
djangochat/asgi.py
import os from django.core.asgi import get_asgi_application from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import chat.routing os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangochat.settings') django_asgi_app = get_asgi_application() application = ProtocolTypeRouter({ "http" : django_asgi_app, "websocket" : AuthMiddlewareStack( URLRouter( chat.routing.websocket_urlpatterns ) ) })
补充说明
- 完全看不到WebSocket握手的迹象;
- 点击提交时,即使消息为空,
console.log无输出,alert也无提示; - 尝试调试WebSocket连接,但因连接未建立,不知如何推进。
问题解决思路
1. 修复JavaScript事件绑定语法错误
代码中点击事件绑定存在语法错误,多了一对括号,导致事件无法正常绑定,表单会执行默认提交行为:
// 错误写法 document.querySelector("#chat-message-submit").onclick() = function(e){...} // 正确写法 document.querySelector("#chat-message-submit").onclick = function(e){...}
2. 添加WebSocket连接的CSRF令牌验证
Channels的WebSocket连接需要携带CSRF令牌,需在创建连接时传入:
{% block scripts %} {{ chat.slug | json_script:"json-chatname"}} {{ request.user.username | json_script:"json-username" }} {% csrf_token %} <script> const chatName = JSON.parse(document.getElementById("json-chatname").textContent); const userName = JSON.parse(document.getElementById("json-username").textContent); // 获取页面中的CSRF令牌 const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; const chatSocket = new WebSocket( "ws://" + window.location.host + "/ws/" + chatName + "/?csrfmiddlewaretoken=" + csrfToken ); // 后续代码不变 </script> {% endblock %}
3. 修复前后端数据字段不匹配问题
前端发送的字段是chatname,但Consumer中尝试获取chat字段,会触发KeyError,需统一字段名:
// 前端修改发送字段为chat chatSocket.send(JSON.stringify({ "message" : message, "username" : userName, "chat" : chatName, }))
4. 修正Consumer中的Group移除方法
Channels中没有group_discard方法,正确移除连接的方法是group_remove:
async def disconnect(self): await self.channel_layer.group_remove( self.chat_group_name, self.channel_name )
5. 确认WebSocket路由配置
确保chat/routing.py的路由配置与前端连接路径匹配:
# chat/routing.py from django.urls import re_path from . import consumers websocket_urlpatterns = [ re_path(r'ws/(?P<chat_name>\w+)/$', consumers.ChatConsumer.as_asgi()), ]
6. 检查Channels全局配置
确保settings.py中正确配置Channels:
INSTALLED_APPS = [ # 其他应用 'channels', 'chat', ] ASGI_APPLICATION = 'djangochat.asgi.application' CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels.layers.InMemoryChannelLayer', # 生产环境建议替换为Redis # 'BACKEND': 'channels_redis.core.RedisChannelLayer', # 'CONFIG': { # "hosts": [('127.0.0.1', 6379)], # }, }, }
内容的提问来源于stack exchange,提问作者Kenshima
相关产品推荐
相关产品推荐

