Django вже давно має репутацію зручного веб-фреймворку. Він допоміг багатьом розробникам та підприємцям втілити свої проекти в життя. Але Django підтримує лише протокол http. А зараз йде революція в світі вебу, і http стає недостатньо. Сьогодні ми використовуємо WebSocket для комунікації в реальному часі, ми використовуємо WebRTC для відеодзвінків і подібного, HTTP/2 теж набирає популярність. Сьогодні кожен сучасний веб-фреймворк повинен підтримувати більше, ніж один протокол. Ось тут в гру і вступають Django Channels (далі я буду звати їх просто "канали"). Мета каналів в тому, щоб додати в Django нові можливості, включаючи підтримку таких сучасних технологій як WebSocket чи HTTP/2.
Як канали працюють?
Принцип їх роботи дуже простий. Щоб зрозуміти його, давайте спочатку розглянемо реальний сценарій їх використання, розглянемо як канали будуть обробляти запит.
http/websocket-запит спочатку потрапляє в зворотний проксі-сервер (наприклад, nginx). Цей крок не обов'язковий, але ж ми гарні розробники, і завжди повинні спочатку направляти запити на загартований зворотній проксі-сервер, перш ніж вони потраплять до нашого додатку.
Nginx віддає запит до серверу нашого додатку. Тепер, коли ми працюємо з декількома протоколами, давайте називати його "Сервер інтерфейсів". Цей сервер знає як саме слід оброблювати запити з використанням різних протоколів. Він приймає запит і трансформує його в message
(повідомлення), а потім відправляє його в channel
(канал).
Ми повинні написати обробників, які будуть слухати певні канали. Коли нове повідомлення надійде в канал, обробник розгляне його і відправить відповідь назад до reply/response channel
, якщо потрібно. Сервер інтерфейсів слухає ці response channels
(канали відповідей), і коли ми передаємо туди повідомлення, він обробляє його і відправляє в зовнішній світ (в даному випадку користувачеві). Обробники запускаються в окремих фонових потоках, тому ви можете створювати їх скільки завгодно.
Як я і казав, принцип роботи дуже простий: сервер інтерфейсів приймає запит і передає його в вигляді повідомлення в канал. Обробник розглядає це повідомлення і відправляє відповідь назад в канал відповіді, сервер інтерфейсів відправляє її користувачеві. Просто і ефективно.
Є вже декілька вбудованих обробників. Наприклад, http.request
може слухати вхідні http-повідомлення. Або websocket.receive
, який може обробляти вхідні websockets-повідомлення. Зазвичай, нам не потрібно обробляти http-повідомлення, сам Django робить це краще. Нас більше цікавить спеціальна логіка для протоколу websockets, або іншого нестандартного протоколу. Окрім вже вбудованих каналів, у нас є можливість створювати власні канали для різних потреб. Канали можуть працювати не лише з протоколами. Наприклад, замість того щоб створювати мініатюру зображення на ходу, цю роботу можна доручити каналу в фоні. За умовчуванням обробники каналів запускаються командою runworker
, що запускає їх в фоні та дозволяє слухати певні канали. На жаль, до сьогодні немає вбудованого механізму, що міг би відслідковувати збої та перезапускати обробники. У зв'язку з цим, Celery може бути гарним вибором для управління фоновими обробниками, що будуть слухати потрібні канали.
Daphne зараз, де-факто, найпопулярніший сервер інтерфейсів, що добре працює з каналами. Канали і передача повідомлення працюють на спеціальному прошарку, що підтримує різні бекенди. Найпопулярнішими є Redis, IPC і зберігання в пам'яті. Проте, зберігання в пам'яті більше призначено для локальних проектів і тестування на машині розробника, в той час як кластер Redis більше підходить для продакшену і може бути легко розширений.
WebSockets echo-сервер
Годі балачок, давайте напишемо щось. Перед усім потрібно встановити потрібний пакет:
pip install channels
Це встановить Django (він є залежністю) і самі channels з потрібними пакетами. Почніть новий проект з djang-admin
та створіть новий додаток.
Тепер додайте channels
в список INSTALLED_APPS
в вашому файлі settings.py
. Для розробки нам вистачить того, щоб канали працювати прямо в пам'яті, давайте вкажемо це в settings.py
:
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "realtime.routing.channel_routing",
},
}
Зверніть увагу на ключ ROUTING
. Його значення має вказувати шлях до поточного маршрутизатора каналів. В моєму випадку я маю додаток з назвою realtime
і модуль routing.py
, що й виконує маршрутизацію каналів.
from channels.routing import route
from .consumers import websocket_receive
channel_routing = [
route("websocket.receive", websocket_receive, path=r"^/chat/"),
]
В цьому списку ми оголошуємо маршрути (route), що дуже схожі на URL-паттерни в Django. Коли ми отримаємо повідомлення від websocket, воно буде передано в каналу websocket.receive
. Тобто тепер нам потрібно створити обробника (consumer
), що буде слухати повідомлення на каналі. Ми також оголосили шлях, з якого потрібно приймати websocket-повідомлення. Якщо його не вказати, то ми будемо отримувати всі websocket-повідомлення для нашого сайту. Іноді це буває корисно, але в більшості випадків слід вказувати конкретну адресу, це допомагає краще організовувати код.
А ось наш consumers.py
:
def websocket_receive(message):
text = message.content.get('text')
if text:
message.reply_channel.send({"text": "You said: {}".format(text)})
Обробник дуже простий, він просто приймає текст і відправляє його назад. Зауважте, що дані запиту доступні в атрибуті content
повідомлення, а reply_channel
- канал, куди ми відправляємо відповідь, звідки її бере сервер інтерфейсів і відправляє назад до websocket'а.
Ми зробили всі необхідні приготування і тепер можемо запустити наш додаток командою python manage.py runserver
. Але зауважте, що так можна робити лише під час розробки, на продакшені слід робити все трішки інакше. Почитати про це можна тут.
Після того, як сервер запущено, відкрийте ваш додаток в браузері. Так, ми не додали жодного view, але не хвилюйтеся, нам вистачить стандартного "It Worked!". Тепер нам потрібно перевірити роботу нашого коду. Відкрийте інструменти розробника в вашому браузері, перейдіть до JS-консолі і вставте наступний код.
socket = new WebSocket("ws://" + window.location.host + "/chat/");
socket.onmessage = function(e) {
alert(e.data);
}
socket.onopen = function() {
socket.send("hello world");
}
Якщо все правильно, ви побачити попап з відправленим повідомленням. Так як ми оголосили, що наші websocket'и працюють лише на деякій сторінці, модифікація URL в JS зробить скрипт несправним. Також ви можете прибрати параметр path
з нашого маршрутизатора, і тоді websocket'и будуть працювати на будь-якій сторінці сайту. Круто, чи не так?
Наш приклад дуже простий, але канали в Django можуть робити набагато більше. Вони дозволяють інтегрувати websocket'и з Django Auth. Подобаються Django's generic views? Не проблема, в нас є generic consumers. Документація теж гарно оформлена, сподіваюсь, вона допоможе вам втілити власні ідеї.
Використання власних каналів
Ми можемо створити власні канали та додати до них обробників. Після чого зможемо відправляти їм повідомлення за іменем. Ось так:
Channel("thumbnailer").send({
"image_id": image.id
})
WSGI чи ASGI?
Через те, що Daphne та ASGI досить нові, деякі розробники віддають перевагу WSGI для обробки http-запитів. В таких випадках, ми можете налаштувати nginx так, щоб він відправляв запити до різних серверів (wsgi / asgi), в залежності від URL, домену чи заголовків.
Ще немає коментарів