В этой статье мы поговорим о каналах Django и о том, как они могут изменить правила игры для фреймворка Django.

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

Цель каналов Django - расширить фреймворк Django, добавив к нему новый уровень для обработки использования WebSockets и фоновых задач.

В качестве примера мы собираемся создать простое веб-приложение под названием coinpricemonitor, чтобы отслеживать цены на биткойны и лайткойны в режиме реального времени. Если вы хотите взглянуть на полный код, он находится здесь. Для создания этого примера я использовал Django 1.11 и Channels 1.1.6.

Что мне нужно изменить, чтобы использовать каналы Django?

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

Если бы Django Channels уже был в ядре Django, как и предполагается в будущем, вам не нужно было бы ничего устанавливать. Вам нужно будет только обновить свой проект до последней версии Django.

Как установить

Хотя Channels еще не входит в ядро ​​Django, нам нужно его установить. Для этого достаточно выполнить команду: pip install channels.

После этого добавьте channels в ваш INSTALLED_APPS кортеж.

Цикл запроса каналов Django

Используя каналы, теперь у вас будет два типа запросов для обработки: HTTP-запросы и запросы веб-сокетов.

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

В следующих разделах мы увидим эти компоненты, которые интегрируют новый цикл запросов с каналами.

Сервер ASGI

В настоящее время платформа Django использует WSGI (интерфейс шлюза веб-сервера), который представляет собой интерфейс для приложений Python для обработки запросов. Но для работы с асинхронными приложениями нам нужно использовать другой интерфейс, который является ASGI (интерфейс шлюза асинхронного сервера), который также может обрабатывать запросы веб-сокетов.

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

У Дафны уже есть каналы, поэтому устанавливать ее не нужно.

Маршрутизатор ASGI

Теперь вы обрабатываете два типа запросов: HTTP-запросы, которые будут перенаправляться на представления как обычно, и запросы веб-сокетов, которые будут перенаправляться потребителям, которые похожи на ваши уже известные представления. Для обработки обоих запросов нам понадобится маршрутизатор. Это часть канального уровня, которая является серверной частью каналов.

Некоторые брокеры, которые могут использоваться в качестве маршрутизаторов:

  • asgi_r edis - Вам необходимо установить R edis для использования этого брокера.
  • asgi_r abbitmq - Вам необходимо установить Rabbitmq для использования этого брокера.
  • сгиреф

Вы можете найти все эти брокеры в PyPI, что означает, что вы можете использовать easy_install или pip для их установки.

Вот пример настройки уровня каналов с asgi_redis брокером:

Настроить слой каналов довольно просто, вот значение каждой клавиши:

  • default - настройки по умолчанию для слоя канала.
  • BACKEND - брокер, которым вы собираетесь пользоваться. В нашем случае asgi_redis.
  • КОНФИГУРАЦИЯ - Конфигурации каналов, такие как хосты и пропускная способность каждого канала. Ознакомиться со всеми вариантами можно здесь.
  • МАРШРУТИЗАЦИЯ - Куда будут направляться входящие сообщения.

Веб-работники

Именно они будут обрабатывать потребителей.

Базовые концепты

Канал

Основная структура данных в каналах - это канал, и его концепция описывается следующим образом:

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

Типы каналов

Один из типов канала - это канал маршрутизации, который в зависимости от того, откуда пришло сообщение, направляется к соответствующему потребителю. Есть три канала маршрутизации:

  • websocket.connect
  • wesocket.receive
  • websocket.disconnect

Вы помните в конфигурации канального уровня, что мы настраивали маршрутизацию? Таким образом, когда приходит сообщение, оно будет перенаправлено одним из каналов маршрутизации внутри файла routing.py. У нас есть три канала маршрутизации: один, когда пользователь пытается запустить соединение через веб-сокет, другой, когда пользователь отправляет сообщение, и третий, когда клиент хочет закрыть соединение.

Когда сообщение поступает в websocket.connect канал, оно будет отправлено ws_connect потребителю, который обработает сообщение.

Другой тип канала - это reply_channel, который поставляется с объектом сообщения. Это идентификатор информации, откуда пришло это сообщение, и того, куда потребитель должен отправить свой ответ.

Потребители и группы

Потребитель - это функция, которая получит сообщение (запрос веб-сокета), обработает его и может вернуть или не вернуть ответ.

Давайте начнем шаг за шагом изучать, как был построен наш coinpricemonitor. Сначала со своим ws_connect потребителем:

Этот потребитель получает сообщение и отвечает флагом accept, который сообщает, что браузер должен принять соединение. Мы можем вернуть всего несколько флагов, кроме accept, давайте посмотрим на них:

accept - принять новое подключение к веб-сокету.

текст - для отправки строки, но вы можете импортировать json и использовать json.dumps() для отправки словаря.

байты - отправляйте зашифрованные данные во внешний интерфейс, чтобы никто не видел, что вы отправляете.

В нашем случае мы используем ключ accept, поэтому, когда сообщение отправлено, оно поступает в метод сокета onOpen во внешнем интерфейсе и выводит в консоль сообщение «Connected to websocket!»:

Обратите внимание, что мы использовали метод send для отправки ответа клиенту, но этот метод взят не из объекта сообщения, а из атрибута сообщения reply_channel. Это еще один тип канала, который представляет канал пользователя, чтобы сообщить, какой канал отправляет ответ.

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

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

Давайте создадим новую группу под названием «btc-price» и добавим в нее канал пользователя, когда он подключается к серверу:

Теперь, когда у нас есть способ группировать каналы, нам действительно понадобится еще одна группа, потому что, помимо отображения цены биткойнов, мы можем изменить, чтобы увидеть цену litecoin (это еще одна криптовалюта). Группа каналов получит цену биткойна, а другой - цену лайткойна.

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

Здесь импортируется channel_session_user. Это декоратор канала, который получает для нас сеанс Django и включает его в объекте сообщения с атрибутом channel_session. Таким образом, новый ключ с именем coin-group добавляется со значением btc-price, чтобы отслеживать, в какой группе находится канал пользователя.

Затем новая цена биткойна отправляется во внешний интерфейс. Я сделал это с помощью периодической задачи на сельдерее, чтобы сделать простой запрос на получение к API bittrex, чтобы получить последнюю цену биткойна и передать эту цену группе btc-price.

Вот вспомогательная функция get_coin_price:

Теперь, когда вы знаете, как каналы обрабатывают соединение, как насчет внешнего интерфейса? Как попасть к ws_connect потребителю? Когда страница загружается, необходимо создать веб-соединение с серверной частью, чтобы начать получать текущую цену биткойнов в режиме реального времени. После запуска события load будет вызвана функция setupWebsocket, которая создаст объект WebSocket с URL-адресом, если проект выполняется локально, вы можете использовать ws://localhost:8000/ws/. Мы настраиваем методы сокета, когда соединение установлено (onopen) и когда приходит сообщение от бэкэнда (onmessage).

После создания объекта WebSocket он попытается подключиться к своему URL-адресу. Это сообщение поступит на уровень канала, который будет перенаправлен на websocket.connect канал, ведущий к ws_connect потребителю.

Другой поток из внешнего интерфейса в серверный - это когда вы нажимаете кнопку, чтобы увидеть litecoin цену. В резюме цена и значок биткойна скрыты и заменены на лайткойн. В конце концов, основная часть: websocket.send(). Это метод веб-сокета, который позволяет отправлять данные через него. Отправляется флаг, чтобы сервер знал, что нужна цена лайткойна, а не биткойна.

Поскольку данные отправляются, сообщение будет перенаправлено на websocket.receive канал, ведущий к ws_receive потребителю.

Здесь мы получаем данные, которые были отправлены из внешнего интерфейса, и делаем оператор if, чтобы проверить, хочет ли пользователь цену Litecoin или цену биткойна. Если пользователю нужна цена лайткойна, мы удалим пользователя из группы каналов, которые получают цену биткойна, и поместим канал пользователя в группу, которая получает цену лайткойна, и изменим значение ключа coin-group ltc-price, чтобы отслеживать канал пользователя. С другой стороны, если клиенту нужна цена биткойна, мы помещаем его канал в группу btc-price и исключаем канал из группы ltc-price и меняем значение ключа coin-group на btc-price.

После изменения вашей группы новое значение будет получено в методе сокета onMessage во внешнем интерфейсе.

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

Последний поток - это поток отключения. Срабатывает при закрытии вкладки браузера. Таким образом, объект Websocket во внешнем интерфейсе вызовет событие отключения от серверной части, чтобы завершить соединение с веб-сокетом.

Сообщение будет перенаправлено на websocket.disconnect канал, который приведет к ws_disconnect потребителю.

ws_disconnect просто удаляет канал пользователя из последней группы, в которой он находился. Чтобы узнать, в какой это была группа, мы проверяем значение в coin-group сохраненном ключе.

Заключение

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

Это практически все, что вам нужно знать, чтобы начать работу с каналами Django. Если вам интересно, вот несколько ссылок, которые могут быть полезны: