1/5 (1) Введение в Django Channels

Добро пожаловать в Django Channels! Каналы меняют Django, чтобы сплести под ним асинхронный код через синхронное ядро, позволяя проектам на Django обрабатывать не только HTTP, но и протоколы, которые требуют подключения соединения на длительное время — WebSockets, MQTT, чатботы, пользовательское радио и многое другое.

Это происходит в то время, когда Django остается синхронным и простым в использовании, позволяя нам выбрать то, как написать наш код: синхронно в стиле Django Views или смешав с асинхронным. Помимо этого он предоставляет интеграцию с такими модулями Django, как система авторизации, система сессий и др., что упрощает, как никогда, расширение нашего проекта, имея только HTTP-протокол, до других протоколов.

Он также связывает эту событийную архитектуру со слоем каналов. Получается система которая позволяет вам легко обмениваться данными между процессами и отделив наш проект от разных процессов.

Черепахи всегда идут вниз

Channels оперируют по принципу «первый вошел — первым вышел». У нас есть единая идея или картина того, что есть приложения в виде Channels и даже самый простой Consumer(эквивалент Views  в Django) является полными работоспособными приложениями ASGI, которые вы можете запускать самостоятельно.

Channels дают вам инструмент, чтобы написать базовые consumers — индивидуальные части, которые могу обрабатывать сообщения чата или уведомления — и связывать их вместе с машрутизацией URL, обнаружением протокола и другими удобными вещами, чтобы создавать полноценное приложением.

Мы рассматриваем HTTP и существующие Django Views, как части большого целого. Традиционно Django Views все также существуют с Channels и все также используемы — мы оборачиваем их в ASGI приложение, называемое channels.http.AsgiHandler . Теперь вы можете написать свой long-polling HTTP — обработчик или приемники WebSocket и бок о бок с существующим кодом Django. URL — маршрутизация и промежуточные модули — это все простые приложения ASGI.

Мы убеждены в том, что вы хотели сохранить возможность использования синхронной техники Django для большинства кода, но иметь опцию прямого асинхронного интерфейса для сложных задач.

Области и события

Channels и ASGI разделяют входящее соединение на 2 компонента: scope(областью) и серия events(событий).

Scope — это набор подробностей о одном входящем соединении — например, путь, по которому веб-запрос был сделан или исходящий IP-адрес WebSocket, или пользовательский обмен сообщениями с чатом — и это сохраняется в течение всего соединения.

Для HTTP протокола scope — это просто один произошедший запрос. Для WebSocket он является жизненным циклом сокета (но изменяется, если сокет закрывается и повторно соединяется).

Для других протоколов это зависит от того, как написана спецификация протокола ASGI. Например, вполне вероятно, что протокол chatbot будет держать одну область открытой для всей беседы пользователя с ботом, даже если базовый протокол чата не имеет статуса.

В течение всего срока действия этой Scope(области) происходит ряд Events(событий). Они представляют пользовательское взаимодействие — создание HTTP — запроса, WebSocket — фрейм и т.д.

Наши каналы или приложения ASGI будут создаваться один раз для каждой Scope(области), а затем будут передаваться поток Events(событий), происходящих в этой области, чтобы решить, что делать.

Пример с HTTP:

  • пользователь создает запрос HTTP;
  • мы открываем новую Scope(область) типа http с деталями пути запроса, метода, заголовков, и т.д.
  • мы отправляем событие  http.request с контентом тела HTTP;
  • приложения Channels или ASG обрабатывают это и генерируют событие http.response для обратного ответа браузеру и закрывает соединение;
  • HTTP — request(запрос)/response(ответ) заканчиваются и Scope(область) уничтожается.

Пример с чатом:

  • пользователь отправляет первое сообщение чату;
  • открывается Scope(область), которая содержит пользовательский ник, выбранного имени и пользовательский ID;
  • программа получает Events(событие) chat.received_message с текстом события. Оно не имеет ответ, должен быть один, два или больше в качестве Events(события) chat.send_message если есть в этом потребность;
  • чем больше пользователь отправляет в чат сообщений тем больше генерируются Events(события) <span chat.received_message
  • после таймаута или когда процесс программы возобновляется Scope(область) закрывается.

В течение срока действия Scope(области) — будь то чат, HTTP-запрос, сокет-соединение или что-то еще — у вас будет один экземпляр приложения, обрабатывающий все события из него, и вы также сможете перенести вещи на экземпляр приложения. Вы можете выбрать, если хотите, сырое приложение ASGI, но Channels(каналы) дают вам удобную абстракцию над ними, называемую Consumers(потребителями).

Что такое Consumer?

Consumer — это потребитель событий и является базовой единицей в коде Channels. Мы называем его Consumer(потребитель), так как он потребляет события, но вы можете подумать об этом, как об ихнем маленьком приложении. Когда приходит запрос или новый сокет, Chanells(каналы) будут следовать их таблице маршрутизации — мы рассмотрим немного этот момент — находят там правильного/нужного Consumer(потребителя) для этих входящих соединений и запустит на выполнение его копию.

Это значит, что в отличии от Views(видов) Django, Consumers(потребители) рассчитаны на запуск в долгое время работы. Они также могут быть запущены на короткое время — ведь, HTTP запросы могут быть обработаны и Consumers(потребителями) — но они построены вокруг идеи жизни в короткий промежуток( они живут в течении Scope(области), которых мы описали выше).

Ниже представлен базовый Consumer(потребитель):

class ChatConsumer(WebsocketConsumer):

    def connect(self):
        self.username = "Anonymous"
        self.accept()
        self.send(text_data="[Welcome %s!]" % self.username)

    def receive(self, *, text_data):
        if text_data.startswith("/name"):
            self.username = text_data[5:].strip()
            self.send(text_data="[set your username to %s]" % self.username)
        else:
            self.send(text_data=self.username + ": " + text_data)

    def disconnect(self, message):
        pass<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

Каждый из разных протоколов имеет разный способ появления событий и каждый тип реализован различным методом. Вы пишете код обработки каждого события и Channels(каналы) позаботятся об их расписании выполнения их всех в параллельном режиме.

Внизу, Channels(каналы) запущены в полном асинхронном событийном цикле и если вы пишете код, похожий выше, то он будет вызван в синхронном потоке. Это означает, что вы безопасно выполнять блокирующие операции, такие как вызов Django ORM:

class LogConsumer(WebsocketConsumer):

    def connect(self, message):
        Log.objects.create(
            type="connected",
            client=self.scope["client"],
        )<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

Однако, если вы хотите больше контроля, и вы готовы работать только в асинхронных функциях, вы можете написать полностью асинхронных Consumers(потребителей):

class PingConsumer(AsyncConsumer):
    async def websocket_connect(self, message):
        await self.send({
            "type": "websocket.accept",
        })

    async def websocket_receive(self, message):
        await asyncio.sleep(1)
        await self.send({
            "type": "websocket.send",
            "text": "pong",
        })<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

Вы можете прочесть больше про Customers(потребителей) по ссылке на официальный сайт.

Маршрутизация и мультипротокольность

Вы можете комбинировать несколько Consumers(потребителей), которые являются, если вспомнить, их собственнымиASGI приложениями, в одно большое приложение, которое будет представляет ваш проект с маршрутизацией:

application = URLRouter([
    url(r"^chat/admin/$", AdminChatConsumer),
    url(r"^chat/$", PublicChatConsumer),
])

Channels(каналы) не только построены вокруг мира HTTP и WebSockets, но также позволяют нам построить любой протокол внутри Django среды, создавая сервер, который отображает эти протоколы в один и тот же набор событий. К примеру, вы можете построить бот чата с стиле:

class ChattyBotConsumer(SyncConsumer):
    def telegram_message(self, message):
        """
        Simple echo handler for telegram messages in any chat.
        """
        self.send({
            "type": "telegram.message",
            "text": "You said: %s" % message["text"],
        })

И затем используем другой маршрутизатор, чтобы один проект мог обслуживать как WebSockets, так и чат-запросы:

application = ProtocolTypeRouter({

    "websocket": URLRouter([
        url(r"^chat/admin/$", AdminChatConsumer),
        url(r"^chat/$", PublicChatConsumer),
    ]),

    "telegram": ChattyBotConsumer,
})

Цель Channels(каналов)- это позволить вам построить ваши собственные Django проекты для работы через любой протокол или транспорт, с которыми вы можете столкнуться в современном WEB, позволяя вам работать со знакомыми компонентами и стилем кодирования, к которому вы привыкли.

Больше информации про протоколы и маршрутизацию можно узнать по ссылке.

Кросспроцессная коммуникация

Как стандартный WSGI-сервер, ваш код приложения, обрабатывающий события протокола, выполняется внутри самого процесса сервера — например, код обработки WebSocket выполняется внутри вашего сервера WebSocket.

Каждый сокет или соединение с вашим общим приложением обрабатывается экземпляром приложения на одном из этих серверов. Они получают вызов и могут напрямую отправлять данные клиенту.

Однако при создании более сложных прикладных систем вам необходимо установить связь между различными экземплярами приложения — например, если вы создаете чат-комнату, когда один экземпляр приложения получает входящее сообщение, он должен распространять его на любые другие экземпляры, которые представляют людей в чате.

Вы можете сделать запрос к базе данных, но Channels(каналы) вносят идею канального слоя, низкоуровневую абстракцию вокруг набора транспортов, которые позволяют вам отправлять информацию между различными процессами. Каждая сущность приложения имеет уникальное канальное имя, и может присоединяться к группам, позволяя отправлять сообщения между точками или широковещать на целую группу.

Канальный слой — это дополнение к Channels(каналам) и может быть запрещен, если он не нужен. для этого нужно установить для CHANNEL_LAYERS пустое значение.

Вы также можете отправлять сообщения в выделенный процесс, который прослушивает самостоятельно, фиксированное имя канала:

# In a consumer
self.channel_layer.send(
    "myproject.thumbnail_notifications",
    {
        "type": "thumbnail.generate",
        "id": 90902949,
    },
)

Вы можете почитать еще больше про канальный слой на офф-сайте Channel Layers.

Интеграция с Django

Channels(каналы) поставляются с простой поддержкой обычных функций Django, таких как сеансы и аутентификация. Вы можете объединить аутентификацию с Views(представлениями) WebSocket, просто добавив к ним правильное middleware:

application = ProtocolTypeRouter({
    "websocket": AuthMiddlewareStack(
        URLRouter([
            url(r"^front(end)/$", consumers.AsyncChatConsumer),
        ])
    ),
})

Для дополнительной информации можно почитать Sessions и Authentication.

Пожалуйста, оцените материал

WebSofter

Web - технологии