Создание чата с использованием Django Channels

| Python

Стандартное Django приложение обрабатывает HTTP запросы, используя рабочий цикл запрос-ответ. Запрос создаётся браузером пользователя, далее он выполняется соответствующим Django view, которое возвращает ответ пользователю. Цикл запрос-ответ не подходит для приложений реального времени, которые требуют частых запросов к серверу. Новые стандарты, такие как websockets и HTTP2 позволяют устранить некоторые из этих недостатков. WebSockets – это новый протокол связи, который обеспечивает полнодуплексные каналы связи по одному TCP соединению и хорошо подходит для приложений реального времени. Открытие и поддержание соединения с сервером с помощью websocket очень дёшево с точки зрения потребления памяти и вычислительных ресурсов процессора.

Пакет Channels расширяет возможности Django, позволяя обрабатывать websocket соединения подобно обычным views. Channels представляет собой упорядоченную FIFO очередь продвигаемых сообщений с доставкой только одному листенеру. Проще говоря, сhannel является очередью задач, которая принимает сообщения от производителей и доставляет их потребителям.

Обработка websocket запроса в Django производится двумя процессами.

  • Интерфейс сервер – обрабатывает входящее соединение по HTTP и Websocket.
  • Рабочий процесс, запускает views для обработки websocket и http запросов.

Обмен данными между ними производится по протоколу ASGI, которые роутятся через Redis. Стоит отметить, поскольку интерфейсный сервер и рабочий процесс в Channels разъединены, можно добавлять и удалять рабочие процессы, не закрывая websocket соединения.

Следующая схема иллюстрирует, как Django обслуживает запросы традиционным способом и с использованием Channels:

Django обрабатывает только HTTP.

Браузер <——–> Web сервер <——–> Django View функция

Django с Channels Браузер <——–> Интерфейс сервер <——–> Слой Channel <——–> Django View функция + Websocket Consumer

Web сервер – работает с http соединениями (спасибо КэП). Интерфейс сервер – обрабатывает HTTP и websocket соединения. Слой Channel – HTTP транспорт websocket сообщения рабочим процессам, которые являются обычными Django представлениями, также известными как обработчики Channels.

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

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

Инструкции по установке Channels.

  1. pip install channels
  2. Добавляем channels в INSTALLED_APPS файла settings.py
  3. Устанавливаем redis

Функциональности чата будут минимальными:

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

Часто упускаемый из виду многими чатами момент неправильного подсчёта количества подключённых пользователей. Это происходит потому, что существует разновидность событий, которые заставляют клиентов отключаться от websocket соединений, не уведомляя об этом сервер. У сервера нет простого способа обновления количества пользователей, подключенных к чат-комнате без периодического процесса дисконнекта. Справиться с этой ситуацией помогает Django пакет django-channels-presence. Он позволяет закрывать устаревшие соединения с веб-сайтом и точно подсчитывает количество подключённых к чату пользователей. Как это работает? Чтобы отслеживать какие websockets всё ещё подключены, необходимо регулярно обновлять временную метку last_seen для всех существующих подключений и периодически удалять соединения, если от них не было отклика непродолжительное время.

Итак, перейдём к коду. У нас будет одна жестко закодированная комната all, для того чтобы не создавать отдельную Room модель. Модель пользовательского сообщения чата:

class ChatMessage(models.Model):
    """
    Модель для представления сообщения чата
    """

    #Поля
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    message = models.TextField(max_length=3000)
    message_html = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        """
        Строка для представления сообщения
        """

        return self.message

Поля модели:

  • user – ForeignKey на модель User;
  • message – необработанный текст, который пользователь вводит в поле чата;
  • message_html – это html-версия сообщения. html версия будет экранирована, в чат-комнате будут разрешены только теги ссылок. Все остальные теги, такие как