Использование Django, Intercooler и Mercure

В предыдущем посте я описал, как Instawork удвоил продуктивность нашей веб-разработки, отказавшись от React и приняв серверные страницы, улучшенные с помощью Intercooler.js. Стандартная установка React / Redux требует большого количества дублированной работы; логика должна быть написана один раз на сервере (как источник истины) и второй раз на клиенте (для управления локальным состоянием). Выполняя весь рендеринг пользовательского интерфейса в нашей кодовой базе Django, мы избавляемся от необходимости писать какую-либо пользовательскую логику приложения на JS. Конечно, чистый серверный рендеринг не обеспечивает того плавного взаимодействия, которого пользователи ожидают от веб-приложений. Вот где приходит Intercooler.js: добавляя несколько атрибутов HTML к нашим элементам, мы можем поддерживать широкий спектр взаимодействий, заменяя контент на странице с помощью AJAX. Весь рендеринг по-прежнему выполняется на стороне сервера, но страницы могут оживать с помощью таких взаимодействий, как бесконечная прокрутка, переключение вкладок, динамические многоступенчатые формы и т. Д.

Я всегда предполагал, что механизм подкачки Intercooler не будет работать для некоторых из более сложных взаимодействий пользовательского интерфейса, которые мы планировали добавить в наше приложение. В частности, меня беспокоило, как мы будем обрабатывать в нашем продукте функции реального времени, такие как чаты или потоковые уведомления. Стоит ли нам отказываться от мечты о нулевом JS, чтобы реализовать эти функции?

С радостью сообщаю, что мои опасения были необоснованными. Мы успешно добавили функции реального времени в наше веб-приложение, не написав ни единой строчки JavaScript! В оставшейся части сообщения будет рассказано, как мы реализовали функцию состояния в реальном времени с помощью Django, Intercooler и события, отправленные сервером с Mercure.

Отправленные сервером события в интерфейсе

События, отправленные сервером (SSE) - это широко поддерживаемый веб-стандарт, который позволяет браузерам отправлять новые данные на веб-страницу в любое время. В отличие от WebSocket, SSE - это простой однонаправленный протокол, реализованный с использованием HTTP на долгоживущем соединении. Веб-страницы могут открывать соединение для получения именованных событий (и данных о событиях) с помощью интерфейса EventSource в JavaScript. Реагирование на события осуществляется путем регистрации обратных вызовов.

Intercooler имеет встроенную поддержку SSE, которая автоматически управляет интерфейсом EventSource. Синтаксис выглядит так:

<div ic-sse-src=”/dashboard/events”>
  <div ic-trigger-on=”sse:status-updated-123" ic-src=”/status/123">  
    Not arrived
  </div>
</div>

Это обычный HTML с двумя специальными атрибутами:

  • ic-sse-src указывает конечную точку для использования при создании EventSource. Поскольку SSE выполняется через HTTP, браузер отправит соответствующие файлы cookie для аутентификации.
  • ic-trigger-on указывает событие, которое запускает обмен содержимым. Специальный синтаксис «sse:» указывает, что данное событие должно инициировать обмен.

Благодаря этим двум атрибутам наш интерфейс полностью поддерживает обновления в реальном времени! Когда интерфейс получает событие status-updated-123, Intercooler запрашивает контент из / status / 123. Ответом будет фрагмент HTML, содержащий новый статус ответа. Затем интеркулер заменяет div статуса новым фрагментом. И все, пользовательский интерфейс обновляется в реальном времени в ответ на события, и нам не нужно было писать какой-либо JavaScript для этого!

Отправленные сервером события на бэкэнде

Конечно, то, что я описал, - простая часть. За кулисами нам необходимо реализовать долгоживущую конечную точку SSE, а также механизм для отправки соответствующих событий всем клиентам. Веб-фреймворки, такие как Django, не очень хорошо справляются с долгоживущими соединениями, поэтому мы искали другие варианты. Проект Django Channels расширяет Django для обработки асинхронной связи, но не поддерживает SSE из коробки. Мы хотели что-то, что поддерживает SSE независимо от языка и фреймворка.

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

  • Клиенты подключаются к долгоживущей конечной точке SSE на сервере-концентраторе. Авторизация выполняется с помощью токена JWT. Маркер кодирует темы событий и цели событий, на которые у клиента есть разрешение.
  • Сервер отправляет события, выполняя простой запрос POST на сервер-концентратор с токеном JWT. Маркер кодирует имя события, тему и цели, которые должны получить событие.
  • После получения запроса POST Mercure Hub отправит событие всем открытым соединениям SSE, которые соответствуют теме и целям.

Мы рассмотрели несколько вариантов и сервисов для обработки SSE-соединений, но в конечном итоге остановились на Mercure по нескольким причинам. Как упоминалось выше, Mercure построен на основе SSE, поэтому его язык и фреймворк не зависят от его языка. Любая система, которая может кодировать токены JWT и делать HTTP-запросы, может использовать Mercure. Мы также ценим, что Mercure - это небольшой микросервис, который работает вместе с нашими серверами, а не как прокси перед нашими серверами (подход, используемый Pushpin). Это означает, что в случае отказа Mercure риск для нашего сайта снизится: мы потеряем функции, работающие в режиме реального времени, но основной сайт продолжит работу. Наконец, мы обнаружили, что он очень стабильный и масштабируемый. В наших нагрузочных тестах Mercure не вспотел даже при работе с 10-кратными нагрузками, которые мы ожидаем.

Наша интеграция Django с Mercure приняла форму вспомогательной функции для отправки событий и миксина представления. Миксин представления делает две вещи:

  • он устанавливает файл cookie mercureAuthorization, используемый клиентом для подключения к концентратору.
  • он добавляет URL-адрес концентратора (с подписанными темами) к данным контекста ответа, чтобы его можно было отобразить как атрибут ic-sse-src.

Я поделился Gist с полным примером наших помощников, миксинов, представлений и шаблонов.

Заключение

Легко подумать, что такие функции, как пользовательский интерфейс в реальном времени, требуют тяжелой клиентской среды. Но благодаря встроенной поддержке SSE в Intercooler, а также простоте настройки и интеграции Mercure, мы смогли придерживаться нашей философии отказа от JS, предоставляя при этом функции, которые ожидают наши пользователи. Мы по-прежнему удивляемся возможностями Django + Intercooler + Mercure, а добавление новых функций реального времени теперь совсем несложно.

Вы добавили в свое веб-приложение обновление в реальном времени? Думаете добавить его в будущем? Мы хотели бы услышать о вашем опыте и решениях или предоставить больше информации о Django, Intercooler или Mercure.