Приветствую всех! Многие разработчики вкурсе про замечательный модуль Nginx Push Module для веб-сервера Nginx. Многие его опробовали, пощупали.
Задача модуля – позволить веб-серверу Nginx выступать в качестве Comet-сервера.
Материала по использованию данного модуля достаточно: хороша официальная страница проекта, описание Basic HTTP Push Relay Protocol, а также многие статьи, например Nginx & Comet: Low Latency Server Push. Однако, во многих руководствах рассматривают лишь базовую конфигурацию модуля с использованием одного общедоступного канала всеми клиентами. Несмотря на огромную полезность, модуль не предоставляет разработчикам гибкое управление каналами, их защиту.
В данной статье я напишу небольшой пример, демонстрирующий возможный способ управления каналами.
Что нам требуется?
Вследствие чего, для каждого пользователя будет выделен свой уникальный канал (после прохождения авторизации, например).
Nginx Push Module предоставляет нам некоторые директивы в конфиге nginx по настройке безопасности. Рассмотрим только те, которые применил я:
Итак, дадим имя нашему модулю – Channel. Разрабатывать его будем на Ruby (также будут иметь место небольшие вставки на Rails).
Для управления каналами (см. Basic HTTP Push Relay Protocol) нам необходим HTTP-клиент. Мне нравится Patron.
Массив открытых каналов будем хранить в массиве opened_channels. Id канала будем генерировать при помощи метода generate_channel_id.
Создание канала (метод open) осуществляется путём отправки PUT-запроса в publish-точку (у нас это просто /publish). При успешном создании нового канала (статус 200) сгенерированный id добавляем в массив opened_channels и возвращаем его.
Закрытие канала (метод close) осуществляется путём отправки DELETE-запроса в publish.
Проверка существования канала (метод exist?) осуществляется с помощью отправки GET-запроса в publish. Если сервер вернул 200 – канал открыт, иначе, удаляем канал из массива.
Отправка данных в канал (метод push) осуществляется посылкой POST-запроса в publish с указанием данных и content-type. Данные отправляем только в открытые каналы.
Все HTTP-запросы должны содержать параметр канала (у нас это channel). Естественно, publish-точку следует защитить.
Задача модуля – позволить веб-серверу Nginx выступать в качестве Comet-сервера.
Материала по использованию данного модуля достаточно: хороша официальная страница проекта, описание Basic HTTP Push Relay Protocol, а также многие статьи, например Nginx & Comet: Low Latency Server Push. Однако, во многих руководствах рассматривают лишь базовую конфигурацию модуля с использованием одного общедоступного канала всеми клиентами. Несмотря на огромную полезность, модуль не предоставляет разработчикам гибкое управление каналами, их защиту.
В данной статье я напишу небольшой пример, демонстрирующий возможный способ управления каналами.
Задача
Что нам требуется?
- cоздание нового канала
- закрытие существующего канала
- проверка существования канала
- отправка данных в канал
- отправка данных во все каналы
Вследствие чего, для каждого пользователя будет выделен свой уникальный канал (после прохождения авторизации, например).
Nginx Push Module — Secure
Nginx Push Module предоставляет нам некоторые директивы в конфиге nginx по настройке безопасности. Рассмотрим только те, которые применил я:
- push_authorized_channels_only [ on | off ]
on – позволить клиенту прослушивание конкретного канала только после его явного создания (отправка POST или PUT запроса в точку publisher). В противном случае при попытке прослушивания закрытого канала клиенту возвращается ответ 403.
off – клиент может начать прослушивание закрытого канала.
- push_max_channel_subscribers [ число ]
Максимальное число одновременных слушателей канала.
Реализация
Итак, дадим имя нашему модулю – Channel. Разрабатывать его будем на Ruby (также будут иметь место небольшие вставки на Rails).
Для управления каналами (см. Basic HTTP Push Relay Protocol) нам необходим HTTP-клиент. Мне нравится Patron.
Массив открытых каналов будем хранить в массиве opened_channels. Id канала будем генерировать при помощи метода generate_channel_id.
Создание канала (метод open) осуществляется путём отправки PUT-запроса в publish-точку (у нас это просто /publish). При успешном создании нового канала (статус 200) сгенерированный id добавляем в массив opened_channels и возвращаем его.
Закрытие канала (метод close) осуществляется путём отправки DELETE-запроса в publish.
Проверка существования канала (метод exist?) осуществляется с помощью отправки GET-запроса в publish. Если сервер вернул 200 – канал открыт, иначе, удаляем канал из массива.
Отправка данных в канал (метод push) осуществляется посылкой POST-запроса в publish с указанием данных и content-type. Данные отправляем только в открытые каналы.
Все HTTP-запросы должны содержать параметр канала (у нас это channel). Естественно, publish-точку следует защитить.
Код модуля:
module Channel @http_client = Patron::Session.new @http_client.base_url = "http://localhost/publish" @@opened_channels = [] mattr_accessor :opened_channels class << self def open id = generate_channel_id resp = @http_client.put(build_request_for_channel(id), "") if resp.status == 200 opened_channels << id id else false end end def close(id) resp = @http_client.delete(build_request_for_channel(id)) resp.status end def exist?(id) resp = @http_client.get(build_request_for_channel id) if resp.status == 200 true else opened_channels.delete id false end end def push(id, data, content_type) if exist? id puts "pushing to channel with id=#{id}..." resp = @http_client.post(build_request_for_channel(id), data, {"Content-Type" => content_type}) resp.status end end def push_to_all_channels(data, content_type="application/json") opened_channels.each { |c| push(c, data, content_type) } end private def generate_channel_id UUIDTools::UUID.timestamp_create.to_s end def build_request_for_channel(id) "/?channel=#{id}" end end end
Открытие канала по запросу:
def subscribe if channel_id = Channel::open render text: channel_id else render nothing: true, status: 500 end end
Пример отправки данных:
user = current_user channel_id = user.channel_id msg = user.messages.last data = msg.to_json(only: [:created_at, :text]) status = Channel::push(channel_id, msg, "application/json")
