Django实现websocket
Django实现websocket
- WebSocket功能
- 使用介绍
- 安装
- 配置
- WebSocket接口--消息接收者的实现
- scope
- 通道层 channel layer
- 部署
- Web客户端连接
- Mac客户端
WebSocket功能
WebSocket长连接一般用于实现实时功能,例如web端的消息通知、会话等场景。
使用 WebSocket 向 Django 项目添加实时功能,一般有两个选择:
-
使用 Django Channels,这是一个向 Django 添加 WebSocket 等功能的项目。 Django 完全支持这种方法。 但是,它需要切换到新的部署架构;
-
在 Django 项目旁边部署一个单独的 WebSocket 服务。
方法2的实现一般可以借助 django-sesame 去管理会话的鉴权,本文不做过多阐述,可以点击参考链接,下面组要介绍方法1中Channels
在Django中的使用。
关于Channels:
- 源码地址 https://github.com/django/channels
- 文档介绍 https://channels.readthedocs.io/en/latest/
使用介绍
安装
pip install -U 'channels[daphne]'
需要使用daphne,是为了后续部署用于启动ASGI1服务。当然,若是你有其他的部署方式你可以仅安装channels。
配置
安装完后,需要在INSTALLED_APPS
配置中添加:
INSTALLED_APPS = ("daphne","django.contrib.auth","django.contrib.contenttypes","django.contrib.sessions","django.contrib.sites",...
)ASGI_APPLICATION = "mysite.asgi.application"
注意:
ASGI_APPLICATION
配置后正常可通过python manage.py runserver
启动服务,低版本Django不行,Django 5.x 版本可行。
添加文件 myproject/asgi.py
from django.core.asgi import get_asgi_application
from django.urls import re_path# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidatorapplication = ProtocolTypeRouter({# 同时能支持原有http协议的接口"http": django_asgi_app,# 增加websocket协议的支持"websocket": AllowedHostsOriginValidator(AuthMiddlewareStack(# 接口路由URLRouter([re_path(r"^chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),]))),
})
WebSocket接口–消息接收者的实现
接口实现可以继承WebsocketConsumer
/ AsyncConsumer
/ AsyncConsumer
,此处介绍WebsocketConsumer,代码示例:
class ChatConsumer(WebsocketConsumer):def connect(self):# django框架会自动处理websocket链接带上的headers信息进行鉴权认证,初始化成用户对象self.user = self.scope['user']self.accept()self.send(text_data="[Welcome %s!]" % self.user.username)def receive(self, *, text_data):if text_data.startswith("/name"):username = text_data[5:].strip()self.send(text_data="[set your username to %s]" % username)else:self.send(text_data=username + ": " + text_data)def disconnect(self, message):pass
值的一提的是,Django框架和Channels结合很好,Auth体系完全兼容,Django http中的header这里也能识别成User用户。
scope
Consumer接受到client端发来的websocket请求后,会将请求信息初始化成scope
,请求相关信息可以从self.scope
中获取,例如:
scope["client"]
client来源ip和port, eg:['127.0.0.1', 6533]
scope["user"]
根据headers等信息初始化的User用户scope["path"]
请求接口path路径scope["query_string"]
连接上的query参数scope["headers"]
请求headersscope["url_route"]
请求path定义路由解析出的参数,eg:{"args": {}, "kwargs": {"pk": 1}}
以上的介绍算是基本实现了一个简单的websoket服务。
若是实现较为复杂的群聊、或者广播通知该如何实现?下面 channel layer
。
通道层 channel layer
安装pip install channels_redis
通道层是一种通信系统。 它允许多个消费者实例相互通信以及与 Django 的其他部分通信。
通道层提供以下抽象:
- 通道是一个可以发送消息的邮箱。 每个频道都有一个名称。 知道频道名称的任何人都可以向该频道发送消息。
- 组是一组相关的通道。 一个团体有一个名字。 任何拥有群组名称的人都可以通过名称向群组添加/删除频道,并向群组中的所有频道发送消息。 不可能枚举特定组中有哪些频道。
每个消费者实例都有一个自动生成的唯一通道名称,因此可以通过通道层进行通信。
需要在Django settings.py
文件中添加配置
# Channels
ASGI_APPLICATION = "mysite.asgi.application"
CHANNEL_LAYERS = {"default": {"BACKEND": "channels_redis.core.RedisChannelLayer","CONFIG": {"hosts": [("127.0.0.1", 6379)],},},
}
接口代码实现
# chat/consumers.py
import jsonfrom asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumerclass ChatConsumer(WebsocketConsumer):def connect(self):# 这里是将群名room_name定义在了path中,通过正则提取self.room_name = self.scope["url_route"]["kwargs"]["room_name"]self.room_group_name = f"chat_{self.room_name}"# Join room groupasync_to_sync(self.channel_layer.group_add)(self.room_group_name, self.channel_name)self.accept()def disconnect(self, close_code):# Leave room groupasync_to_sync(self.channel_layer.group_discard)(self.room_group_name, self.channel_name)# Receive message from WebSocketdef receive(self, text_data):text_data_json = json.loads(text_data)message = text_data_json["message"]# Send message to room groupasync_to_sync(self.channel_layer.group_send)(self.room_group_name, {"type": "chat.message", "message": message})# Receive message from room groupdef chat_message(self, event):message = event["message"]# Send message to WebSocketself.send(text_data=json.dumps({"message": message}))
部署
服务启动 daphne -b 0.0.0.0 -p 8000 myproject.asgi:application
其他参数可以查看 daphne文档 。
websocket协议nginx代理示例配置如下:
upstream channels-backend {server localhost:8000;
}
...
server {...location / {try_files $uri @proxy_to_app;}...location @proxy_to_app {proxy_pass http://channels-backend;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_redirect off;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Host $server_name;}...
}
Web客户端连接
前端JS可以通过 WebSocket 实现连接,注意浏览器兼容问题即可。
// Create WebSocket connection.
const socket = new WebSocket("ws://localhost:8000/chat/room/");// Connection opened
socket.addEventListener("open", function (event) {socket.send("Hello Server!");
});// Listen for messages
socket.addEventListener("message", function (event) {console.log("Message from server ", event.data);
});
Mac客户端
借助工具 websocat , 可通过 brew install websocat
安装。
websocat ws://localhost:8000/chat/room/
# 加header
websocat -H="Authorization: Token xxxx" ws://localhost:8000/chat/room/
需要注意的是,
-H=""
中间等号不能用空格替代,否则无法生效,希望作者在之后的版本中个可以兼容这个问题。
可以关注下Django框架中提供的WSGI和ASGI的区别; ↩︎