Приветствую всех! Многие разработчики вкурсе про замечательный модуль 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")