Greetings to all! Many developers know about the great
Nginx Push Module for the
Nginx web server. Many have tested it, felt it.
The task of the module is to allow the Nginx web server to act as a
Comet server .
There is enough material to use this module: the
official project page is good, the
Basic HTTP Push Relay Protocol is described as well as many articles, such as
Nginx & Comet: Low Latency Server Push . However, in many manuals only the basic configuration of the module is considered using a single public channel by all clients. Despite its enormous utility, the module does not provide developers with flexible channel management and their protection.
')
In this article I will write a small example showing a possible way to manage channels.
Task
What do we need?
- creating a new channel
- closing an existing channel
- channel existence check
- sending data to the channel
- sending data to all channels
As a result, each user will be allocated a unique channel (after passing authorization, for example).
Nginx Push Module - Secure
Nginx Push Module provides us with
some directives in the nginx configuration configuration security. Consider only those that I applied:
- push_authorized_channels_only [on | off]
on - allow the client to listen to a specific channel only after it has been explicitly created (sending a POST or PUT request to the publisher point). Otherwise, when you try to listen to a closed channel, the response 403 is returned to the client.
off - the client can start listening to a closed channel.
- push_max_channel_subscribers [number]
Maximum number of simultaneous channel listeners.
Implementation
So, let's name our module - Channel. We will develop it in Ruby (there will also be small inserts on Rails).
To control the channels (see
Basic HTTP Push Relay Protocol ), we need an HTTP client. I like
Patron .
The array of open channels will be stored in the array opened_channels. The channel id will be generated using the generate_channel_id method.
Creating a channel (the open method) is done by sending a PUT request to the publish point (we just have it / publish). If the new channel is created successfully (status 200), then the generated id is added to the opened_channels array and returned.
Closing a channel (the close method) is done by sending a DELETE request to publish.
Checking the existence of a channel (the exist? Method) is performed by sending a GET request to publish. If the server returns 200, the channel is open, otherwise, we delete the channel from the array.
Sending data to the channel (push method) is carried out by sending a POST request to publish with the indication of data and content-type. Data is sent only to open channels.
All HTTP requests must contain a channel parameter (we have this channel). Naturally, the publish point should be protected.Module code:
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=
Channel opening on request:
def subscribe if channel_id = Channel::open render text: channel_id else render nothing: true, status: 500 end end
Example of sending data:
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")