Модуль 35

Асинхронність та Django Channels

Ось твій урок. Приготуйся, сьогодні ми змінимо те, як ти сприймаєш веб-запити!


🎓 Урок: Асинхронність та Django Channels (ASGI)

Привіт, світе! 👋 Радий бачити вас на цьому занятті.

Сьогодні ми переступимо поріг класичного Django і увійдемо у світ, де все відбувається миттєво. Ми поговоримо про асинхронність та магію Django Channels.


1. 🔥 Вступ: Коли "F5" вже не рятує

Уявіть собі, що ви пишете месенджер. Найпростіший чат. Ви — користувач А, ваш друг — користувач Б.

Ви відправляєте повідомлення: "Привіт, як справи?". Ваш сервер приймає його, зберігає в базі даних і каже вам: "Окей, збережено".

Але що бачить ваш друг? Нічого. Поки він не оновить сторінку (не натисне F5), він не дізнається, що ви йому написали.

Питання до вас: Чи зручно було б користуватися Telegram або Instagram, якби вам доводилося оновлювати екран щосекунди, щоб перевірити нові повідомлення? Звісно, ні. Це було б жахливо.

У чому проблема? Класичний Django працює за схемою HTTP: Запит → Відповідь. Клієнт запитав — сервер відповів — зв'язок розірвався. Сервер не може сам "постукати" до клієнта і сказати: "Гей, тут тобі лист прийшов!".

Аналогія: Уявіть, що ви чекаєте важливий лист. * Синхронний підхід (HTTP): Ви ходите до поштової скриньки кожні 5 хвилин. Пусто. Пусто. Пусто. Ви втомлюєтесь. * Асинхронний підхід (WebSockets): Поштар (сервер) дзвонить вам у двері тільки тоді, коли лист реально прийшов. Ви займаєтесь своїми справами, а реагуєте лише на дзвінок.

Ось для цього "дзвінка у двері" нам і потрібні WebSockets та Django Channels. Без них сучасний Real-time веб (чати, сповіщення, живі графіки, ігри) просто неможливий.


2. 🧠 Теоретична база: Як це працює "під капотом"

Щоб зрозуміти Channels, треба зрозуміти дві абревіатури: WSGI та ASGI.

1. WSGI (Синхронний Django)

Це старий добрий стандарт. Він працює лінійно: один процес обробляє один запит за раз. Якщо запит довгий (наприклад, чекаємо відповідь від іншого API), сервер просто "зависає" і чекає. Це як черга в касу супермаркету: поки касир не проб’є товар бабусі попереду, ви стоїте.

2. ASGI (Асинхронний Django + Channels)

Це новий стандарт (Asynchronous Server Gateway Interface). Він дозволяє Django обробляти багато подій одночасно, не чекаючи завершення попередніх.

Що треба запам'ятати (обов'язково): * WebSockets: Це постійний "тунель" між браузером і сервером. Він відкритий увесь час. Дані можуть летіти туди й назад у будь-який момент. * Consumer (Споживач): В Channels це аналог View (відображення). Але якщо View обробляє HTTP-запит, то Consumer обробляє повідомлення з веб-сокету. * Channel Layers (Шари каналів): Це як "поштова служба" всередині Django. Вона дозволяє різним користувачам "спілкуватися" між собою (наприклад, переслати повідомлення з кімнати "Чат А" всім, хто в ній сидить).

Інтуїтивне розуміння: Django Channels перетворює ваш сервер з "бібліотекаря", який видає книги по одній, на "диспечера таксі", який одночасно слухає рацію, приймає замовлення і відправляє машини, не припиняючи розмову ні на секунду.


3. 🧪 Приклади (від простого до реального)

Щоб Channels запрацювали, нам треба встановити daphne (ASGI-сервер) та налаштувати asgi.py замість wsgi.py. Але припустімо, що налаштування вже зроблені.

Приклад 1: Ехо-бот (Найпростіший)

Питання: Якщо я надішлю серверу слово "Hello", що має зробити найпростіший WebSocket сервер? Правильно, він має відправити це саме слово назад.

# consumers.py
import json
from channels.generic.websocket import WebsocketConsumer

class EchoConsumer(WebsocketConsumer):
    # 1. Клієнт постукав (connect)
    def connect(self):
        self.accept() # Ми кажемо: "Заходь!"

    # 2. Клієнт щось надіслав (receive)
    def receive(self, text_data):
        data = json.loads(text_data)
        message = data['message']

        # 3. Відправляємо назад
        self.send(text_data=json.dumps({
            'message': f"Ти сказав: {message}"
        }))

Пояснення: Це синхронний код всередині Channels. Ми просто прийняли з'єднання і на будь-яку фразу відповідаємо нею ж.


Приклад 2: Чат-кімната (Реальний сценарій)

Тепер складніше. Юзер А пише повідомлення, а Юзер Б має його побачити. Тут нам потрібні групи.

# consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = "general_chat"
        self.room_group_name = f"chat_{self.room_name}"

        # Додаємо поточного юзера в групу "general_chat"
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )
        await self.accept()

    async def disconnect(self, close_code):
        # Видаляємо з групи при відключенні
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # Отримуємо повідомлення від WebSocket (від клієнта)
    async def receive(self, text_data):
        data = json.loads(text_data)
        message = data['message']

        # Відправляємо повідомлення ВСІЙ ГРУПІ (а не тільки собі)
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message', # Це назва методу-обробника нижче
                'message': message
            }
        )

    # Цей метод викликається, коли група отримує повідомлення
    async def chat_message(self, event):
        message = event['message']

        # Відправляємо фінальне повідомлення своєму клієнту (в браузер)
        await self.send(text_data=json.dumps({
            'message': message
        }))

Що тут відбулося? 1. Клієнт підключився → ми додали його в "кімнату" (Layer). 2. Клієнт написав → ми переслали це в Layer. 3. Layer розіслав це всім учасникам кімнати. 4. Кожен учасник отримав це повідомлення у свій браузер.


4. 🛠 Практична частина

Час кодити! Не бійтеся помилятися — саме так ми вчимося.

Завдання 1: Ping-Pong Створіть Consumer, який при отриманні повідомлення "Ping" відповідає "Pong". На будь-яке інше повідомлення відповідає "I don't understand".

Завдання 2: Живий годинник (JavaScript + Django) Напишіть JavaScript код на стороні клієнта, який з’єднується з вашим сокетом і виводить у консоль браузера все, що приходить. Підказка: const chatSocket = new WebSocket('ws://...' + window.location.host + '/ws/chat/');

Завдання 3: "А що, якщо..." (Обробка розриву) Змініть код так, щоб коли користувач виходить з чату (disconnect), у консоль сервера виводилося повідомлення: "Користувач покинув чат :( ".

Завдання 4: Реальний кейс (Сповіщення) Уявіть, що у вас є модель Order (Замовлення). Коли статус замовлення змінюється (наприклад, через адмінку Django), клієнт має миттєво побачити попап "Ваша піца готова!". Підказка: Вам знадобляться Django Signals (post_save), щоб викликати відправку повідомлення в Channels Layer.

Міні-кейс для самостійного вирішення: Створіть просту гру "Хто перший натисне кнопку". Сервер надсилає сигнал "START". Хто перший надішле відповідь — той переміг, а іншим приходить "Ви програли".


5. 💡 Мислення як у розробника

А тепер послухайте уважно. Ось де новачки ламають собі зуби.

⚠️ Типова помилка №1: Блокування потоку

Ви пишете async def, але всередині використовуєте синхронні функції, які довго "думають" (наприклад, time.sleep(5) або важкий запит до БД без адаптера). Результат: Весь ваш асинхронний сервер зупиняється і чекає. Інші користувачі в шоці. Як думає профі: "Якщо я в асинхронному коді, я маю використовувати await asyncio.sleep(5), а для роботи з БД — спеціальні асинхронні методи Django ORM (await User.objects.aget(...)) або обгортку database_sync_to_async".

⚠️ Типова помилка №2: WebSockets для всього

Новачки, дізнавшись про Channels, намагаються переписати на них весь сайт. Порада: Не треба. WebSockets — це дорого для сервера (тримає з'єднання відкритим). Використовуйте їх тільки там, де потрібен реал-тайм. Для завантаження списку статей звичайний HTTP — все ще король.

⚠️ Типова помилка №3: Забути про Redis

У прикладах ми використовуємо пам'ять (InMemoryChannelLayer). Це ок для тесту. Але якщо ви запустите це в продакшн з декількома серверами — користувачі на Сервері 1 не побачать повідомлень від Сервера 2. Рішення: Завжди використовуйте Redis як бекенд для Channel Layers у реальних проектах.


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? 1. Зрозуміли, чому натискати F5 — це минуле століття. 2. Розібрали різницю між WSGI (синхронним) та ASGI (асинхронним). 3. Навчилися створювати Consumers, які тримають постійний зв'язок з клієнтом. 4. Зрозуміли концепцію Groups, щоб робити чат-кімнати.

Тепер ви вмієте: Створювати інтерактивні веб-додатки, які реагують на події миттєво. Ви більше не обмежені схемою "запит-відповідь".

🚀 Що далі? Асинхронність — це круто, але що робити, якщо треба виконати задачу, яка займає 10 хвилин (обробка відео, експорт звіту)? Браузер не буде чекати. На наступному уроці ми познайомимося з Celery — вашим найкращим другом для фонових задач.

А поки що — вперед до коду! Якщо щось не працює — читайте логи, вони ваші найкращі друзі. Щасти! 👨‍💻👩‍💻