Django实现websocket
WebSocket功能
WebSocket长连接一般用于实现实时功能,例如web端的消息通知、会话等场景。
使用 WebSocket 向 Django 项目添加实时功能,一般有两个选择:
-
使用 Django Channels,这是一个向 Django 添加 WebSocket 等功能的项目。 Django 完全支持这种方法。 但是,它需要切换到新的部署架构;
-
在 Django 项目旁边部署一个单独的 WebSocket 服务。
方法2的实现一般可以借助 django-sesame 去管理会话的鉴权,本文不做过多阐述,可以点击参考链接,下面组要介绍方法1中Channels
在Django中的使用。
关于Channels:
使用介绍
安装
pip install -U 'channels[daphne]'
需要使用daphne,是为了后续部署用于启动ASGI^1^服务。当然,若是你有其他的部署方式你可以仅安装channels。
配置
安装完后,需要在INSTALLED_APPS
配置中添加:
python
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
python
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 AllowedHostsOriginValidator
application = 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,代码示例:
python
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
文件中添加配置
python
# Channels
ASGI_APPLICATION = "mysite.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
接口代码实现
python
# chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class 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 group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name, self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name, self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name, {"type": "chat.message", "message": message}
)
# Receive message from room group
def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
self.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 实现连接,注意浏览器兼容问题即可。
javascript
// 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
安装。
shell
websocat ws://localhost:8000/chat/room/
# 加header
websocat -H="Authorization: Token xxxx" ws://localhost:8000/chat/room/
需要注意的是,
-H=""
中间等号不能用空格替代,否则无法生效,希望作者在之后的版本中个可以兼容这个问题。
- 可以关注下Django框架中提供的WSGI和ASGI的区别; ↩︎