Привет!
Пару месяцев назад я опубликовал на Хабре статью, посвященную описанию open-source проекта Centrifuge. Напомню, что это сервер рассылки сообщений подключенным клиентам (в основном из веб-браузера) в реальном времени. Написан на Python.
С тех пор я продолжал работать над проектом в свободное время и сейчас готов поделиться накопившимися мыслями и изменениями.
Изначально, Центрифуга была самобытным проектом. Не сильно заботясь о воспроизведении функционала существующих аналогов, я писал код так, как казалось правильным мне самому. В итоге сообщения клиентам доставлялись, всё работало, но! Было ли удобно всем этим пользоваться? Нет!
В конце июня я наткнулся на великолепную статью от Serge Koval — Python and real-time Web. Удивительно, но на тот момент я не знал о существовании Faye. Статья открыла мне этот замечательный проект, как и понимание того факта, что все-таки Центрифуга в своем текущем состоянии не сильно упрощает жизнь при разработке real-time веб-приложений.
С того времени я допиливал Центрифугу с прицелом на удобство использования и с оглядкой на pusher.com, pubnub.com и Faye.
Вопрос, зачем мне нужно было писать код с нуля, если уже существуют более матерые и продвинутые аналоги, неизбежен. Вот несколько причин:
Теперь расскажу об изменениях, произошедших с момента написания предыдущей статьи о Центрифуге.
Структура — проекты, пространства имен и их настройки — теперь по умолчанию будут храниться в SQLite — базе данных, входящей в стандартную библиотеку Python. Поэтому при запуске процессов Центрифуги на одной машине больше нет необходимости в установке PostgreSQL или MongoDB, как было ранее. Так как Центрифуга рассчитана на использование в небольших и средних проектах — я считаю, это важное и нужное изменение, так как одной машины должно хватить сполна.
Можно пойти чуть дальше — и запустить Центрифугу со структурой, описанной в конфигурационном файле. При этом теряется возможность динамически вносить и сохранять изменения из веб-интерфейса, но зато нет никаких зависимостей от внешнего хранилища. Данная возможность также чрезвычайно помогает при разработке.
Появилась поддержка presence и history — теперь можно узнать, кто в данный момент подключен к каналу, а также получить последние сообщения, отправленные в канал. Для хранения этих данных используется Redis. Если Redis не настроен — данные просто не будут доступны клиентам, ничего при этом не сломается.
Возникает вопрос. Сейчас Центрифуга использует ZeroMQ PUB/SUB сокеты для коммуникации между несколькими своими процессами. Быть может, раз в игру вступил Redis в качестве хранилища информации о подключенных клиентах и истории сообщений, то стоит использовать его PUB/SUB возможности и для коммуникации между процессами Центрифуги вместо ZeroMQ? В том единственном сравнительном бенчмарке, который я видел, ZeroMQ по производительности опережает Redis.
Поэтому на данный момент я оставил все как есть. Однако это спорный и важный момент.
Еще теперь можно получать сообщения о подключении(отключении) клиента к каналу (от канала). Приятный пустячок.
Наконец, самое, пожалуй, важное — появился javascript-клиент — обертка над протоколом Центрифуги. Он построен на основе Event Emitter, написанного Оливером Калдвеллом (Oliver Caldwell). Теперь взаимодействовать с Центрифугой из браузера очень просто. Примерно вот так:
За бортом в этом примере остались настройки аутентификации (о них можно прочитать в документации). Также обратите внимание на название канала — оно состоит из имени пространства имен, которое должно быть создано в административном интерфейсе до подключения, в данном случае это
Авторизация в такого рода приложениях, пожалуй, самая сложная часть. Как я уже упоминал, в Faye нужно писать расширения на NodeJS или Ruby для защиты доступа к определенным каналам. Pusher.com для приватных каналов предлагает следующую схему:
При попытке подписаться на приватный канал, отправляется AJAX запрос на бэкенд вашего приложения с именем канала. В случае, если доступ разрешен, вы должны вернуть подписанный ответ, который в дальнейшем вместе с именем канала отправляется непосредственно в Pusher. Преимущество здесь в том, что ваше приложение на момент получения AJAX-запроса в большинстве случаев уже содержит объект текущего пользователя (например, в Django это
В Центрифуге применяется немного иной подход. Идентификатор текущего пользователя отправляется один раз в момент подключения — его вы указываете при конфигурации javascript-клиента вместе с ID проекта и токеном. Токен — это HMAC, сгенерированный на основе секретного ключа проекта (о котором должен знать только бэкенд вашего приложения), ID проекта и ID пользователя. Токен необходим для проверки корректности переданных ID проекта и ID юзера. В дальнейшем при подписке на приватные каналы Центрифуга будет отправлять POST запрос вашему приложению со строковыми ID юзера, именем пространства имен и именем канала. Поэтому первым делом в функции-обработчике авторизации вам нужно будет получить объект своего пользователя по ID.
Еще один важный момент, касающийся авторизации — сейчас, чтобы подписаться на несколько каналов приходится несколько раз вызывать функцию
Хотелось бы отметить еще один способ защиты приватных данных, который никто не отменял. Например, чтобы сделать отдельные приватные каналы для каждого пользователя приложения — можно генерировать трудно угадываемые имена каналов на основе какого-либо секретного ключа и ID пользователя. В таком случае вполне можно обходиться без дополнительной авторизации, по крайней мере до тех пор, пока вашим клиентам не выгодно делиться именами своих приватных каналов:)
Есть возможность добавить кастомную асинхронную функцию (обрамленную tornado-декоратором
Установка Центрифуги в самом простом случае сводится к одной команде
Вот в этом разделе документации я постарался как можно подробней объяснить как работает Центрифуга, какие есть опции запуска, какой адрес указывать при подключении из браузера и многое другое. На английском, правда.
Нагрузочное тестирование пока не проводил. Надеюсь, займусь бенчмарками в ближайшее время. Интересно сравнить с Faye, интересно запустить на PYPY. Ну и, конечно, необходимо продолжать работу над устойчивостью к всевозможным ошибкам, совершенствовать Python-код и javascript-клиент и так далее. Присоединяйтесь!
Спасибо за внимание!
Пару месяцев назад я опубликовал на Хабре статью, посвященную описанию open-source проекта Centrifuge. Напомню, что это сервер рассылки сообщений подключенным клиентам (в основном из веб-браузера) в реальном времени. Написан на Python.
С тех пор я продолжал работать над проектом в свободное время и сейчас готов поделиться накопившимися мыслями и изменениями.
Изначально, Центрифуга была самобытным проектом. Не сильно заботясь о воспроизведении функционала существующих аналогов, я писал код так, как казалось правильным мне самому. В итоге сообщения клиентам доставлялись, всё работало, но! Было ли удобно всем этим пользоваться? Нет!
В конце июня я наткнулся на великолепную статью от Serge Koval — Python 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 для приватных каналов предлагает следующую схему:
При попытке подписаться на приватный канал, отправляется 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-клиент и так далее. Присоединяйтесь!
Спасибо за внимание!