Использование 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.