WebSocket в Django
Что такое WebSocket?
Ну, это протокол компьютерной связи, который используется для двунаправленной связи.
Он может использоваться сервером для отправки данных клиенту.
Короче говоря, они используются для связи в реальном времени.
Пример: чат-приложения, синхронизация данных в реальном времени в играх, SSE и т.д.
Хотя WebSocket кажется крутым, он может быть слишком перегружен для простых приложений, таких как приложение погоды, которое опрашивает данные через несколько интервалов.
Как вы можете реализовать их в Django?
Что ж, это легко, мы можем использовать пакет python, называемый channels.
Они созданы теми же разработчиками, что и Django.
Просто откройте терминал и введите следующее, чтобы установить channels
python -m pip install -U channels["daphne"]
Он установит каналы и специальный веб-сервер ASGI под названием daphne, который используется для обработки протокола ws://.
Теперь в этом посте мы будем делать индикатор присутствия пользователя в сети. Это приложение сообщит клиенту количество пользователей, подключенных к серверу.
Также мы не будем использовать channel_layer для трансляции сообщения, так как это слишком усложнит ситуацию.
Создайте приложение django
django-admin startproject django_websocket
Перейдите в папку django_websocket и введите эту команду, чтобы создать новое приложение django.
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, чтобы позволить django также обрабатывать 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: Позвольте нам обрабатывать различные типы интерфейсов протоколов, например, дляhttpмы просто будем делать вещи http, но для соединений протокола WebSocket мы позволим нашему приложению обрабатывать часть веб-сокетов.AuthMiddlewareStack: Поддерживает стандартную аутентификацию Django, при которой данные пользователя хранятся в сеансе. Он разрешает доступ только для чтения к пользовательскому объекту вscope. Короче говоря, это позволяет нам получить текущийrequest.user.PresenceConsumer: Это наш заказной потребитель ASGI, который будет обрабатывать веб-сокеты. Мы будем делать это дальше- В строке
8мы добавляем файл, который добавляет проектsettings.pyв окружение с именем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],
}
)
)
Этот код может показаться пугающим, но на самом деле он довольно прост, если вы понимаете основную концепцию потребителей.
Давайте сломаем это.
- Со строк
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"], так как мы обернули наш PreescenceConsumer в AuthMiddlewareStack, мы можем получить текущего пользователя из сеанса и сохранить его в файле self.user. После того, как мы получили пользователя, мы собираемся добавить текущее пользовательское соединение к connection, а затем вызвать self.update_indicator и передать msg как connected.
Теперь, если мы посмотрим на update_indicator, мы увидим, что мы перебираем список connection и отправляем всем клиентам данные json.
Пример:
Если пользователь с именем Sid подключается к WebSocket с 3 пользователями, подключенными, все клиенты, подключенные к 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.
Это похоже на URL-адрес, отображающий ваши представления в типичном приложении django.
Теперь в основном в очень больших приложениях мы должны просто разделить маршрутизацию представлений и маршрутизацию ws, но здесь, чтобы сэкономить время, мы просто напишем это в urls.py
Сначала создайте файл 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-адресов для наших представлений и WebSocket.
Как видите, мы также используем представления аутентификации django, чтобы получить функции входа и выхода в нашем приложении.
Чтобы добавить WS Consumer, нам просто нужно отобразить его в urlpatterns, как и любые другие представления на основе классов, только вместо .as_view() здесь мы пишем .as_asgi().
Это была backend часть, теперь нам нужно настроить нашу frontend часть.
Теперь давайте перейдем к frontend части
- Создайте каталог
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 для стилизации нашей страницы, вы можете использовать свою собственную таблицу стилей, если хотите.
Главное, что мы должны здесь посмотреть, это javascript.
- Во-первых, мы создаем объект
WebSocketи передаем URL-адрес WebSocket. - Далее мы сохраняем элементы html с помощью
getElementById. - Затем мы обработаем событие
WebSocket.onmessageдля сообщений в реальном времени с сервера.Eventсодержит атрибутdata, к которому мы будем использоватьJSON.parseдля преобразования приходящего JSON в объект. - Когда WebSocket получит сообщение от службы, мы покажем, что пользователь подключен в div
#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.
Мы видим, что Django использует сервер ASGI/Daphne для запуска этого веб-сайта.
Теперь откройте http://localhost:8000, и вы должны увидеть следующую страницу.
Вы увидите, что в окне сообщения будет написано AnonymousUser Connected.
Давайте создадим учетную запись и протестируем ее.
python manage.py createsuperuser
Теперь, когда мы создали суперпользователя, давайте войдем под этой учетной записью и посмотрим.
Теперь откройте частную страницу и откройте обе страницы в режиме разделенного просмотра.
Демонстрационное видео
Вот ссылка GitHub для исходного кода этого приложения: https://github.com/foxy4096/django_websocket_demo
Так что за дело, ребята.