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
- Add the websocket-rails gem to your Gemfile and run the bundle command
- 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"; } }
Reblogged this on Sutoprise Avenue, A SutoCom Source.
Reblogged this on bob-roberts.net and commented:
Great write up for Websockets and Nginx (and you should be using Nginx!!)
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.
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?