WebSocket در جنگو – انجمن DEV

WebSocket چیست؟
خوب، آنها پروتکل ارتباطی کامپیوتری هستند که برای برقراری ارتباط دو طرفه استفاده می شود.
می تواند توسط سرور برای ارسال داده به مشتری استفاده شود.
به طور خلاصه، از آنها برای ارتباط بلادرنگ استفاده می شود.
به عنوان مثال: برنامه های چت، همگام سازی داده های بلادرنگ در بازی ها، SSE و غیره.
به نظر می رسد WebSocket جالب به نظر می رسد، آنها می توانند برای برنامه های ساده مانند یک برنامه آب و هوا که داده ها را بین چند بازه نظرسنجی می کند بیش از حد پر شده باشند.
چگونه می توانید آنها را در جنگو پیاده سازی کنید؟
خوب، به راحتی می توانیم از یک بسته پایتون به نام استفاده کنیم channels
.
آنها توسط همان توسعه دهندگانی ساخته شده اند که جنگو را ساخته اند.
فقط یک ترمینال را باز کنید و برای نصب کانال ها عبارت زیر را تایپ کنید
python -m pip install -U channels["daphne"]
این کانال ها و یک وب سرور ویژه ASGI به نام نصب می کند daphne
که برای رسیدگی به آن استفاده می شود ws://
پروتکل
اکنون در این پست، یک نشانگر حضور کاربر آنلاین ایجاد می کنیم. این برنامه تعداد کاربران متصل به سرور را به مشتری می گوید.
همچنین ما از آن استفاده نخواهیم کرد channel_layer
برای پخش پیام، زیرا اوضاع را بیش از حد پیچیده می کند.
یک برنامه جنگو ایجاد کنید
django-admin startproject django_websocket
برو به django_websocket
پوشه و این دستور را تایپ کنید تا یک برنامه جنگو جدید ایجاد کنید.
cd django_websocket
python manage.py startapp presence
که در settings.py
اضافه کردن daphne
سرور و ما presence
برنامه
اضافه کردن
daphne
به بالا.
# django_websocket/settings.py
INSTALLED_APPS = [
'daphne' # ⬅ ASGI Webserver
'presence', # ⬅ Our Custom App
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
خطوط زیر را در قسمت اضافه کنید settings.py
به جنگو اجازه می دهد تا ASGI را نیز مدیریت کند و همچنین تغییر مسیر ورود و خروج را مدیریت کند.
# django_websocket/settings.py
# Daphne Conf
ASGI_APPLICATION = "django_websocket.asgi.application"
LOGIN_REDIRECT_URL = "index"
LOGOUT_REDIRECT_URL = "index"
حالا بیایید کمی کد اضافه کنیم asgi.py
و کد را با این جایگزین کنید:
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter # <- Add this
from channels.auth import AuthMiddlewareStack # <- Add this
from presence.consumers import PresenceConsumer # <- Add this
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_websockets.settings')
application = ProtocolTypeRouter(
{
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
PresenceConsumer.as_asgi()
),
}
)
بیایید آن را تجزیه کنیم:
- از خط
1
به6
ما همه ماژول های مورد نیاز را وارد می کنیم-
os
: برای تنظیم برخی از متغیرهای محیطی مورد نیاز -
get_asgi_application
: فقط سمت http برنامه ما را کنترل می کند. -
ProtocolTypeRouter
: به ما اجازه می دهد تا انواع مختلفی از رابط های پروتکل را مدیریت کنیم، مانند forhttp
ما فقط کارهای http را انجام می دهیم، اما برای اتصالات پروتکل WebSocket به برنامه ما اجازه می دهیم تا با بخش وب سوکت ها کار کند. -
AuthMiddlewareStack
: از احراز هویت استاندارد جنگو پشتیبانی می کند، جایی که جزئیات کاربر در جلسه ذخیره می شود. این اجازه می دهد تا دسترسی فقط خواندنی به یک شی کاربر درscope
. به طور خلاصه به ما امکان می دهد جریان را دریافت کنیمrequest.user
-
PresenceConsumer
: مصرف کننده ASGI سفارشی ما است که سوکت های وب را مدیریت می کند. در ادامه این را خواهیم ساخت - خط
8
جایی است که ما در حال اضافه کردن فایل هستیم و پروژه را اضافه می کنیمsettings.py
در env به نامDJANGO_SETTINGS_MODULE
. ما نباید نگران آن باشیم. - از خط
10
به17
جایی است که ما برنامه را اعلام می کنیم، همان است"django_websocket.asgi.application
.
-
- برنامه برابر است با
ProtocolTypeRouter
که دیکشنری از پروتکل هایی مانندhttp
وwebsocket
- WebSocket تابعی به نام می گیرد
AuthMiddlewareStack
که مصرف کنندگان ما را می گیرد
اکنون به بخش جالبی میرسیم که خود WebSocket را به مصرفکنندگان تبدیل میکند.
ابتدا یک نام فایل ایجاد کنید consumers.py
که در presence
پوشه، و این کد را در آن بنویسید.
import json
from channels.generic.websocket import WebsocketConsumer
class PresenceConsumer(WebsocketConsumer):
connections = []
def connect(self):
self.accept()
self.user = self.scope["user"]
self.connections.append(self)
self.update_indicator(msg="Connected")
def disconnect(self, code):
self.update_indicator(msg="Disconnected")
self.connections.remove(self)
return super().disconnect(code)
def update_indicator(self, msg):
for connection in self.connections:
connection.send(
text_data=json.dumps(
{
"msg": f"{self.user} {msg}",
"online": f"{len(self.connections)}",
"users": [f"{user.scope['user']}" for user in self.connections],
}
)
)
اکنون این کد ممکن است ترسناک به نظر برسد، اما در واقع زمانی که مفهوم اصلی Consumers را درک کنید، بسیار ساده است.
بیایید آن را تجزیه کنیم.
- از خط 1 تا 3، ما چند ماژول مهم مانند
json
وWebsocketConsumer
. -
از خط 6 ما در حال ایجاد یک کلاس به نام هستیم
PresenceConsumer
که ازWebsocketConsumer
- این
WebsocketConsumer
برخی از روشهای WebSocket را به ما میدهد-
connect
متد زمانی فراخوانی می شود که کلاینت به WebSocket متصل شود. اگر این روش را نادیده بگیریم، باید اضافه کنیمself.accept()
. -
disconnect
متد زمانی فراخوانی می شود که کلاینت به WebSocket قطع شود. -
receive
متد زمانی فراخوانی می شود که مشتری درخواستی را به WebSocket ارسال کند
-
- این
-
روش نامگذاری شده
update_indicator
یک روش سفارشی است که ما ایجاد کرده ایم. -
این
connections
یک لیست خالی است که کاربران متصل را ذخیره می کند.
در connect
روش، ما روشها را نادیده میگیریم تا وقتی یک کاربر جدید به WebSocket متصل شد، کاربر را از جلسه با این کد دریافت میکنیم. self.user = self.scope["user"]
، همانطور که خود را بسته ایم PrescenceConsumer
که در AuthMiddlewareStack
، می توانیم کاربر فعلی را از جلسه دریافت کرده و در قسمت ذخیره کنیم self.user
. پس از دریافت کاربر، میخواهیم اتصال کاربر فعلی را به آن اضافه کنیم connection
و سپس فراخوانی self.update_indicator
و عبور از msg
مانند connected
.
حال اگر به داخل نگاه کنیم update_indicator
ما می توانیم ببینیم که ما در حال حلقه زدن به آن هستیم connection
لیست و ارسال تمام مشتری یک داده json.
مثال:
اگر کاربری به نام Sid با 3 کاربر متصل به WebSocket متصل شود، همه کلاینت های متصل به WebSocket از جمله Sid، json زیر را دریافت خواهند کرد.
{
"msg": "Sid Connected",
"online": 4,
"users" [
"user1",
"user2",
"user3",
"Sid"
]
}
اکنون برخی از نماهای خود را برای نمایش صفحه فهرست و صفحه ورود بررسی می کنیم. فقط این 4 خط کد را اضافه کنید و کارتان تمام است
from django.shortcuts import render
def index(request):
return render(request, "index.html")
بله همین است.
اکنون به مسیریابی WebSocket می پردازیم.
این دقیقاً مانند نقشهبرداری آدرس اینترنتی از نماهای شما در یک برنامه معمولی جنگو است.
در حال حاضر بیشتر در برنامه های کاربردی در مقیاس بسیار بزرگ، ما فقط باید مسیریابی view ها و مسیریابی ws را از هم جدا کنیم، اما در اینجا برای صرفه جویی در زمان، فقط می خواهیم آن را در urls.py
- ابتدا a ایجاد کنید
urls.py
فایل درpresence
برنامه و کد زیر را اضافه کنید.
from django.urls import path
from . import consumers
from . import views
from django.contrib.auth.views import LoginView, LogoutView
urlpatterns = [
path('', views.index, name="index"),
path('login/', LoginView.as_view(template_name='login.html'), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path("ws/presence", consumers.PresenceConsumer.as_asgi()),
]
در اینجا ما در حال ایجاد یک نقشه URL برای views و WebSocket خود هستیم.
همانطور که می بینید، ما همچنین از نماهای احراز هویت جنگو برای دریافت قابلیت ورود و خروج در برنامه خود استفاده می کنیم.
برای اضافه کردن WS Consumer، فقط باید آن را در نقشه برداری کنیم urlpatterns
درست مانند هر نمای کلاس دیگری، فقط به جای .as_view()
، اینجا می نویسیم .as_asgi()
.
اوه! این قسمت باطن بود، حالا باید قسمت جلویی خود را راه اندازی کنیم.
حالا بیایید به قسمت جلویی برسیم
- ایجاد یک
template
دایرکتوری درpresence
برنامه و ایجاد دو فایل به نامindex.html
وlogin.html
در index.html
کد زیر را بنویسید
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Online Presence Indicator</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
<div class="container">
<section class="section">
<div class="columns">
<div class="column">
{% if user.is_authenticated %}
Hello <strong>{{ user }}</strong>
<br>
<a href="{% url 'logout' %}">Logout</a>
{% else %}
<a href="{% url 'login' %}">Login</a>
{% endif %}
<h1 class="title">Online Presence Indicator</h1>
<div id="presence"><span class="tag is-success" id="pre_cnt">0</span> users online</div>
<ul id="messages"></ul>
</div>
<div class="column">
<div class="box">
<h1 class="title">Online Users</h1>
<div id="online-users"></div>
</div>
</div>
</div>
</section>
</div>
<script>
const ws = new WebSocket('ws://localhost:8000/ws/presence/');
const presenceEl = document.getElementById('pre_cnt');
const messagesEl = document.getElementById('messages');
const onlineUsers = document.querySelector("#online-users");
ws.onmessage = (event) => {
onlineUsers.innerHTML = "";
let data = JSON.parse(event.data)
presenceEl.innerHTML = data.online;
const li1 = document.createElement('li');
li1.innerHTML = data.msg;
messagesEl.appendChild(li1);
data.users.forEach(user => {
const li2 = document.createElement("li");
li2.classList.add("on-us")
li2.innerHTML = user;
onlineUsers.appendChild(li2);
});
};
</script>
</body>
</html>
در اینجا ما از Bulma برای استایل دادن به صفحه خود استفاده می کنیم، در صورت تمایل می توانید از شیوه نامه خود استفاده کنید.
نکته اصلی که در اینجا باید به آن نگاه کنیم جاوا اسکریپت است.
- در ابتدا ما در حال ایجاد یک
WebSocket
شی و ارسال url WebSocket. - سپس عناصر html را با استفاده از آن ذخیره می کنیم
getElementById
. - سپس ما هستیم
WebSocket.onmessage
دسته رویداد به پیام های بلادرنگ از سرور. اینevent
حاوی الفdata
ویژگی، که برای آن استفاده خواهیم کردJSON.parse
برای تبدیل JSON آینده به شی - هنگامی که WebSocket پیامی را از سرویس دریافت می کند، نشان خواهیم داد که کاربر به آن متصل است
#messages
div و کاربر را به آن اضافه کنید#online-users
بخش
حالا بیایید به سرعت صفحه ورود خود را ایجاد کنیم.
باز کن login.html
و این html را بنویسید.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Online Presence Indicator</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
<div class="container">
<section class="section">
<h1 class="title">Login</h1>
<form action="." method="post">
{{form.as_p}}
{% csrf_token %}
<br>
<button class="button">Login</button>
</form>
</section>
</div>
</body>
</html>
فقط همین و اکنون درخواست ما کامل شده است.
حالا بیایید با اجرای سرور آن را آزمایش کنیم
python manage.py runserver
شما باید جریان زیر را ببینید.
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
May 23, 2023 - 22:54:21
Django version 4.2.1, using settings 'myproject.settings'
Starting ASGI/Daphne version 4.0.0 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
می بینیم که جنگو از سرور ASGI/Daphne برای اجرای این وب سایت استفاده می کند.
اکنون http://localhost:8000 را باز کنید و باید صفحه زیر را ببینید.
صفحه فهرست با کاربر احراز هویت نشده
خواهید دید که جعبه پیام نوشته شده است AnonymousUser Connected
بیایید یک حساب کاربری ایجاد کنیم و آن را آزمایش کنیم.
python manage.py createsuperuser
حالا که superuser را ایجاد کردیم، بیایید با این حساب وارد شوید و ببینید.
صفحه ورود
صفحه فهرست با کاربر تایید شده
حالا یک صفحه خصوصی باز کنید و هر دو صفحه را در نمای تقسیم شده باز کنید
ویدئوی نمایشی
https://www.youtube.com/watch?v=evlpSU-qQoI
این لینک GitHub برای کد منبع این برنامه است: https://github.com/foxy4096/django_websocket_demo
پس این برای آن بچه ها.
به سلامتی ✌