WebSocket over Nginx

How often do you find that the awesome movie seats you have painstakingly chosen for yourself, is suddenly not available when you proceed to pay for it. “Damn it! Just missed!” you say. Wouldn’t it be  great to be guaranteed the seat you selected everytime!

The problem with the above scenario (as is  with most e-commerce portals) is that, if two users almost simultaneously  select same seat and checkout to book, the seat will be booked with the user who has clicked first. The other user will be redirected to same page with message the Seat has already been booked. We all have used Indian Railway Website and this problem really gets me frustrated.So I wanted to build system in which the seat I select always gets booked.

To avoid this problem, I came up with two solutions

Auto Refresh every 2 seconds (like cricket scores on cricinfo.com)

  • This would increase load on server as there will be database query every 2 seconds and  again the load will increase with increase in concurrent users using the page.
  • This would not create a real-time user experience.

Websockets

WebSockets is a web technology providing full-duplex communications channels over a single TCP connection. It is similar to radio broadcasting. It consists of a broadcaster (radio transmitter) and subscribers (radio receivers). A user connects to a particular channel (frequency) and whatever is published over that channel is received by the user. Similarly, in the Websocket space, the broadcaster is a server and the subscriber is the browser.

So, in the earlier scenario, whenever a seats get booked, the server will broadcast message to all subscribers instantaneously. With the help of this message, wecan change the color of a booked seat immediately on all browsers that have the page open. This ensures

  • Low load on server.
  • A Real Time user experience

I thought of using websocket-rails gem as it makes it pretty easy to implement WebSocket in rails.  It is built over faye (a publish-subscribe messaging system based on the Bayeux Protocol).

Getting started with websocket-rails

The Configuration

  1. Add the websocket-rails gem to your Gemfile and run the bundle command
  2. Run the installation generator:   The generator will require the WebSocket javascripts  in our application.js                                                    
    bundle exec rails g websocket_rails:install

A lot more information related to setup is here

Connecting to the server

You connect to the server by creating a new WebSocketRails object and passing it the server address.

# app/views/bookings/index.html.haml
javascript:
  var dispatcher = new WebSocketRails('localhost:3001/websocket')

Event Router and Trigger events on the server from the browser client

The installation generator will create the events.rb file in your config/initializers directory. All the routes related to websockets are to be added here. You can edit this file to begin mapping client side events to controller actions.

# config/initializers/events.rb
subscribe :hold_seat, to: UserController, with_method: :hold_seat

This events triggered from the JavaScript client will invoke methods on the controller on the server. For example, from the browser JavaScript console, we can trigger the UsersController#hold_seat method with the following command

dispatcher.trigger('hold_seat', {seat_number: 12});

Triggering Events on the Client form server

  • Broadcasting to all connected clients

You can trigger an event on the client using the broadcast_message method. The broadcast_message method takes two arguments, the event name to trigger(function to invoke on client side), and an object to send along with the event.

# app/controller/users_controller.rb:
broadcast_message :update_seat_status, {number: seat.number, state: 'hold'}
  •  Broadcasting to subscribed clients

The channel will be automatically created the first time a client subscribes to the channel or a message is broadcasted to the channel on the server.

To listen to events on channel we can add this JavaScript on our page

# app/assets/javascripts/websocket.js
channel = dispatcher.subscribe(channel_name);

To publish to a specific channel, we can publish a message to that channel from anywhere in a Rails application using the following code syntax:

WebsocketRails[:channel_name].trigger(:event_name, object_to_send)
  • Publishing events from the browser client
channel.trigger('event_name', object_to_send);

A full fledged working Rails application for booking seats using Websocket-rails is available on my github account

Caveats

  • I used passenger as application server and  as far passenger 3 is concerned it does not support  WebSockets. Passenger 4 does support websockets but I have not tried it yet. Instead, I used thin as a standalone server. In this situation only  the page which needs WebSocket connection was rendered using the standalone server and rest of the application works on passenger.  We also need to use Redis to enable publishing channel events to the standalone server.

Configuration to enable standalone server mode

# config/initializers/events.rb
WebsocketRails.setup do |config|
  config.standalone = true
end

For more configuration click here

  • Use Thin server as primary app server.

Protocol Upgradation from http to websocket protocol

It is required to change the protocol form http to websockcet for creating websocket. Nginx did announce its support for protocol upgradation for websocket in its 1.3.13 version (Feb 2103).  We just to need configure nginx to change protocol  from http to websocket protocol for a specific route.  In below configuration protocol upgradation is done using  proxy_set_header option.

server {
  listen   80;
  server_name  app.local;
  root  /home/app/public;
  passenger_enabled on;
  rails_env development;

  location /any_location {
     proxy_pass http://localhost:3001/realtime_page;
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
  }
}
Advertisements

About Pratik Shah

Ruby on Rails developer
This entry was posted in Ruby on Rails and tagged , . Bookmark the permalink.

5 Responses to WebSocket over Nginx

  1. Pingback: WebSocket over Nginx | has many :code_blocks

  2. Bob Roberts says:

    Reblogged this on bob-roberts.net and commented:
    Great write up for Websockets and Nginx (and you should be using Nginx!!)

  3. Camden Narzt says:

    I wonder if it would be too much trouble to ask you to update the post to say Passenger Enterprise 5 (standalone or with Nginx integration) fully supports websockets and protects against slow clients too.

    • Foo Funster says:

      It’s a 3 year old post. If you posted more helpful replies on the passenger mailing list rather than vague, flippant ones, that too would have helped. But do you care?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s