Centrifuge набирает обороты

в 7:32, , рубрики: asynchronous, open source, python, sockjs, tornado, WebSocket, zeromq, Веб-разработка, метки: , , , , , ,

Centrifuge набирает обороты Привет!

Пару месяцев назад я опубликовал на Хабре статью, посвященную описанию open-source проекта Centrifuge. Напомню, что это сервер рассылки сообщений подключенным клиентам (в основном из веб-браузера) в реальном времени. Написан на Python.

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

Изначально, Центрифуга была самобытным проектом. Не сильно заботясь о воспроизведении функционала существующих аналогов, я писал код так, как казалось правильным мне самому. В итоге сообщения клиентам доставлялись, всё работало, но! Было ли удобно всем этим пользоваться? Нет!

В конце июня я наткнулся на великолепную статью от Serge KovalPython and real-time Web. Удивительно, но на тот момент я не знал о существовании Faye. Статья открыла мне этот замечательный проект, как и понимание того факта, что все-таки Центрифуга в своем текущем состоянии не сильно упрощает жизнь при разработке real-time веб-приложений.

С того времени я допиливал Центрифугу с прицелом на удобство использования и с оглядкой на pusher.com, pubnub.com и Faye.

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

  • Это интересно. В проекте на данный момент используются Tornado, ZeroMQ, Redis, SockJS, Bootstrap 3. Прекрасные инструменты, работать с которыми — безграничное счастье.
  • Pusher.com и Pubnub.com — облачные сервисы, не всегда возможно/есть_смысл/хочется полагаться на третью сторону. Невозможно внести изменения в серверную часть.
  • Аналогов на Python я так и не нашел (может быть вы знаете?), бэкенд Faye — это Ruby или NodeJS. Чтобы сделать авторизацию подписки в канал нужно писать расширения на этих языках. Я же хотел создать более независимое от языка бэкенда веб-приложения решение, предоставляющее необходимый функционал из коробки.
  • Некоторые особенности, которых нет в аналогах. Это наличие пространств имен, определяющих особенности поведения принадлежащих им каналов. Веб-интерфейс для управления проектами, их настройками и возможностью в реальном времени следить за сообщениями в каналах.

Теперь расскажу об изменениях, произошедших с момента написания предыдущей статьи о Центрифуге.

Во-первых, структура — проекты, пространства имен и их настройки — теперь по умолчанию будут храниться в SQLite — базе данных, входящей в стандартную библиотеку Python. Поэтому при запуске процессов Центрифуги на одной машине больше нет необходимости в установке PostgreSQL или MongoDB, как было ранее. Так как Центрифуга рассчитана на использование в небольших и средних проектах — я считаю, это важное и нужное изменение, так как одной машины должно хватить сполна.

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

Появилась поддержка presence и history — теперь можно узнать, кто в данный момент подключен к каналу, а также получить последние сообщения, отправленные в канал. Для хранения этих данных используется Redis. Если Redis не настроен — данные просто не будут доступны клиентам, ничего при этом не сломается.

Возникает вопрос. Сейчас Центрифуга использует ZeroMQ PUB/SUB сокеты для коммуникации между несколькими своими процессами. Быть может, раз в игру вступил Redis в качестве хранилища информации о подключенных клиентах и истории сообщений, то стоит использовать его PUB/SUB возможности и для коммуникации между процессами Центрифуги вместо ZeroMQ? В том единственном сравнительном бенчмарке, который я видел, ZeroMQ по производительности опережает Redis.
Поэтому на данный момент я оставил все как есть. Однако это спорный и важный момент.

Еще теперь можно получать сообщения о подключении(отключении) клиента к каналу (от канала). Приятный пустячок.

Наконец, самое, пожалуй, важное — появился javascript-клиент — обертка над протоколом Центрифуги. Он построен на основе Event Emitter, написанного Оливером Калдвеллом (Oliver Caldwell). Теперь взаимодействовать с Центрифугой из браузера очень просто. Примерно вот так:

var centrifuge = new Centrifuge({
    // настройки аутентификации
});
 
centrifuge.on('connect', function() {
    // соединение с Центрифугой установлено
 
    var subscription = centrifuge.subscribe('python:django', function(message) {
        // функция, вызываемая при получении нового сообщения из канала
    });
 
    subscription.on(‘ready’, function() {
        subscription.presence(function(message) {
            // получена информация о подключенных к каналу клиентах
        });
        subscription.history(function(message) {
            // история последних сообщений канала
        });
        subscription.on('join', function(message) {
            // вызывается, когда новый клиент подключается к каналу
        });
        subscription.on('leave', function(message) {
            // вызывается когда клиент отключается от канала
        });
    });
 
});
 
centrifuge.on('disconnect', function(){
    // соединение с Центрифугой потеряно
});
 
centrifuge.connect();

За бортом в этом примере остались настройки аутентификации (о них можно прочитать в документации). Также обратите внимание на название канала — оно состоит из имени пространства имен, которое должно быть создано в административном интерфейсе до подключения, в данном случае это python. Непосредственно имя канала указывается после — в данном случае это django. Пространство имен определяет настройки всех принадлежащих ему каналов. В настройках проекта можно выбрать пространство имен по умолчанию — тогда в javascript-коде можно не указывать явно название пространства имен. То есть, в случае если пространство имен python является дефолтным для проекта, можно писать вот так:

centrifuge.on('connect', function() {
    var subscription = centrifuge.subscribe('django', function(message) {
        console.log(message);
    });
});
 

Авторизация в такого рода приложениях, пожалуй, самая сложная часть. Как я уже упоминал, в Faye нужно писать расширения на NodeJS или Ruby для защиты доступа к определенным каналам. Pusher.com для приватных каналов предлагает следующую схему:

Centrifuge набирает обороты

При попытке подписаться на приватный канал, отправляется AJAX запрос на бэкенд вашего приложения с именем канала. В случае, если доступ разрешен, вы должны вернуть подписанный ответ, который в дальнейшем вместе с именем канала отправляется непосредственно в Pusher. Преимущество здесь в том, что ваше приложение на момент получения AJAX-запроса в большинстве случаев уже содержит объект текущего пользователя (например, в Django это request.user).

В Центрифуге применяется немного иной подход. Идентификатор текущего пользователя отправляется один раз в момент подключения — его вы указываете при конфигурации javascript-клиента вместе с ID проекта и токеном. Токен — это HMAC, сгенерированный на основе секретного ключа проекта (о котором должен знать только бэкенд вашего приложения), ID проекта и ID пользователя. Токен необходим для проверки корректности переданных ID проекта и ID юзера. В дальнейшем при подписке на приватные каналы Центрифуга будет отправлять POST запрос вашему приложению со строковыми ID юзера, именем пространства имен и именем канала. Поэтому первым делом в функции-обработчике авторизации вам нужно будет получить объект своего пользователя по ID.

Еще один важный момент, касающийся авторизации — сейчас, чтобы подписаться на несколько каналов приходится несколько раз вызывать функцию subscribe на клиентской стороне. Если каналы приватные, то каждая такая подписка будет приводить к POST запросу к вашему приложению. Не оптимизированное поведение, которое хотелось бы улучшить. Но тот же pusher.com, признавая, что такие случаи хоть и редки, но бывают среди требований их клиентов, пока в полной мере эту проблему не решил. Здесь я пока в поиске правильного пути решения.

Хотелось бы отметить еще один способ защиты приватных данных, который никто не отменял. Например, чтобы сделать отдельные приватные каналы для каждого пользователя приложения — можно генерировать трудно угадываемые имена каналов на основе какого-либо секретного ключа и ID пользователя. В таком случае вполне можно обходиться без дополнительной авторизации, по крайней мере до тех пор, пока вашим клиентам не выгодно делиться именами своих приватных каналов:)

Есть возможность добавить кастомную асинхронную функцию (обрамленную tornado-декоратором @coroutine) перед публикацией сообщения в канал. Внутри этой функции можно делать с сообщением все что угодно, в том числе вернуть None и тем самым отменить публикацию сообщения. Но, пожалуй, это мало кому пригодится, как и аналогичная возможность добавить обработчик, вызываемый после публикации. Это достаточно низкоуровневое вмешательство и требует знания Python и Tornado.

Установка Центрифуги в самом простом случае сводится к одной команде pip install centrifuge внутри virtualenv. Однако на машине должен быть установлен ZeroMQ (libzmq3) и dev-пакет для PostgreSQL (сам PostgreSQL сервер необязателен). Найденные проблемы, которые могут возникнуть при установке из PYPI, и способ их решения описаны в документации. Запуск одного процесса выполняется командой centrifuge. Однако для запуска в боевую среду потребуется конфигурационный файл, так как в нем содержатся важные настройки безопасности. Также не обойтись без использования дополнительных опций командной строки, если вы хотите запустить несколько процессов.

Вот в этом разделе документации я постарался как можно подробней объяснить как работает Центрифуга, какие есть опции запуска, какой адрес указывать при подключении из браузера и многое другое. На английском, правда.

Нагрузочное тестирование пока не проводил. Надеюсь, займусь бенчмарками в ближайшее время. Интересно сравнить с Faye, интересно запустить на PYPY. Ну и, конечно, необходимо продолжать работу над устойчивостью к всевозможным ошибкам, совершенствовать Python-код и javascript-клиент и так далее. Присоединяйтесь!

Спасибо за внимание!

Автор: FZambia

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js