Django и приложения реального времени

Веб претерпел ряд трансформаций, и старый добрый механизм "запрос-ответ" перестал существовать, поскольку приложения становятся все сложнее. Это означает, что для их разработки требуется больше инструментов, и это может стать интересным для разработчиков, обучающихся их использованию, но, с другой стороны, это делает все более сложным.

КАНАЛЫ - КОНЦЕПЦИИ

Существует множество фреймворков для разработки приложений реального времени. В своем докладе "Создание приложений реального времени" на DjangoCon Europe я рассказал о том, как это сделать с помощью Django и, в частности, с помощью Channels, фреймворка для создания асинхронных приложений, который позволяет выйти за рамки типичного механизма "запрос-ответ" HTTP.

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

По сравнению с предыдущей версией, релиз 2.0 Channels принес большие изменения. Channels 1.0 позволял людям писать только синхронный код, скрывая асинхронный, тогда как в новом релизе появился и асинхронный интерфейс, так что разработчик волен решать, какой подход использовать.   

Channels использует протокол Asynchronous Server Gateway Interface (ASGI) и реализует его на каждом уровне: каждая часть Channels представляет собой ASGI-приложение, способное работать автономно. Такая структура делает программное обеспечение модульным, позволяя разработчику создавать свой собственный конвейер.

Первый элемент, который должен быть установлен - это сервер протоколов, часть программного обеспечения, которая взаимодействует с сетью, поскольку она перехватывает вызовы и переводит все в ASGI для приложения. С websocket и HTTP мы можем использовать Daphne. Как только соединение установлено, создается область видимости. В нем собираются все данные о самом соединении, и они должны быть направлены через маршрутизацию. В то время как scope обеспечивает связь между соединением и экземпляром приложения, маршрутизация направляет сообщения потребителю. 

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

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

ДЕМОНСТРАЦИЯ, КОТОРУЮ Я РАЗРАБОТАЛ

Для демонстрации потенциала каналов я разработал веб-приложение, которое позволяет выполнять следующие действия:

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

КАКИЕ ASGI ПРИЛОЖЕНИЯ ВАМ НУЖНЫ ДЛЯ СОЗДАНИЯ

Для создания этого приложения я работал на трех уровнях : 

  • Конфигурация слоев каналов
  • маршрутизация
  • потребители

СЛОИ КАНАЛОВ

ASGI_APPLICATION = 'dashboard.routing.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('localhost', 6379)],
        },
    },
}

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

МАРШРУТИЗАЦИЯ

Для этого приложения я установил три маршрутизатора, которые работают последовательно. 

# equivalent to my_project.urls
# tipically used to include application routing
application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
            path('status/', documents_routing),
        ])
    ),
})

Первый заданный элемент - это маршрутизация протокола, и в данном конкретном случае мы будем использовать только websocket. И после этого middleware, который "мигрирует" аутентификационные данные Django на scope.

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

channel_routing = URLRouter([
    path('users/', UserCounterConsumer),
    path('documents/', DocumentListConsumer),
    path('document/<str:slug>/<str:phase>/', DocumentDetailConsum
])

ПОТРЕБИТЕЛЬ

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

При рассмотрении потребителя websocket есть три основных события, которые должны быть установлены и управляться:

  • подключение
  • разъединение
  • прием (сообщения)

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

class UserCounterConsumer(JsonWebsocketConsumer):
    groups = 'users',

    def connect(self):
    """ Increment users on connect and notify other consumers"""
    super().connect()
    if self.scope['user'].is_authenticated:
        increment_users(message.user)
    msg = {'users': count_users(),
    'type': 'users.count'}
    async_to_sync(self.channel_layer.group_send)('users', msg

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

def users_count(self, event):
    """ Notify connected user """
    self.send_json(content=event['message'])

После отправки сообщения приложение вызывает метод users.count, которым управляет user_count. Короче говоря, создается пользовательское событие, управляемое функцией с определенным именем. 

В рамках мониторинга параллелизма группы используются более широко. 

class DocumentListConsumer(JsonWebsocketConsumer):
        @property
        def groups(self):
            return Document.Status.list,

        def connect(self):
            super(DocumentListConsumer, self).connect()
            async_to_sync(self.channel_layer.group_send)(
                 self.slug, {
                     'type': 'document.status',
                     'message': self.get_status_packet()
        })

        def document_status(self, event):
            self.send_json(content=event['message'])

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

Вкратком виде ядро этого приложения представлено этими строками кода

def connect():
    ...
    async_to_sync(self.channel_layer.group_send)(
         self.slug, {
             ...
    })

 

def connect():
    ...
    async_to_sync(self.channel_layer.group_send)(
         self.slug, {
             ...
    })

 

def connect():
    ...
    async_to_sync(self.channel_layer.group_send)(
         self.slug, {
             ...
    })

 

def connect():
    ...
    async_to_sync(self.channel_layer.group_send)(
         self.slug, {
             ...
    })

Этот метод позволяет отправлять сообщения, создавать события, которые управляются потребителями, и позволяет синхронной функции взаимодействовать с асинхронной. Connect - это элемент, позволяющий отправить сообщение группе. В данном конкретном случае отправляемым сообщением является словарь, но вы можете отправлять различные типы сообщений. Единственным обязательным элементом для установки является тип сообщения, поскольку он определяет события, которые могут быть созданы.  
Для того чтобы приложение работало, было установлено событие document.status. 
Все потребители, подключенные к группе slug, должны реализовать метод, описанный выше. 

Мы надеемся, что смогли вдохновить вас и что вы дадите Channels шанс. Следите за новостями о приложениях Django и советами по разработке!

Вернуться на верх