WebSockets with Cramp

Rails itself is not good at asynchronous event handling. And because of this it is bad at push techniques and also not a very good candidate for real time web applications. This problem exists in Rails because we can't keep a bidirectional socket open between our client and a Rails application.

There are many solution that solve this problem. Node.js and Socket.io are some javascript based solutions. Whereas Faye, Goliath, async_sinatra and Cramp are some Ruby based solutions.

Cramp is an asynchronous framework running inside EventMachine loop. It means that while Cramp is communicating with a client over a connection, it can handle another connection with some other client simultaneously. And all of it is done by Cramp in a very scalable fashion. You should give it a shot for your next massive multiplayer online game.

Cramp implements solutions concerning real time web applications like long polling, streaming and WebSockets. In built support for WebSockets was introduced in version 0.9.

WebSockets are W3C specifications to create a bidirectional connection between a client and a server. This specification defines a fairly decent javascript API with which we can create a WebSocket connection between a client browser and a server. Following are a few javascript functions that let us open and communicate over a WebSocket connection from our browser to a server.

Cramp provides us a Cramp::Websocket class which lets us create our own Ruby class with WebSocket support. It also lets us define some methods or actions that are reflection of callback function provided by WebSocket API.

There is code of a small example chat application at github that you can check out. I will explain relevant portions of code right here.

Above is our main Cramp and WebSocket enabled Ruby script. Lets decode it line by line.

On first line, we require cramp for all the WebSocket goodies. Cramp itself does not yet have any rendering engine in itself. We use erubis to render our home page once a user tries to load it in his browser. On line three we require usher. It is a simple ruby gem that is used to define routes for rack applications. Finally we tell cramp to use thin as its backend server. Rainbows is other server that is currently supported by cramp.

We create a HomeAction class within ChatRamp namespace and make it inherit from Cramp::Action. Cramp::Action is very much like ActionController::Base of Rails. start action is executed when a user goes to home page of our application. It simply renders index.erb. There is much to Cramp::Action but we wont go into it in this article.

Cramp::Websocket is the class where all the Websocket action is. We define some callback methods. user_connected simply puts the WebSocket connection created between our application and browser in @@users class variable. user_left simply removes that connection whenever this connection is terminated. And message_received loops through all current connections and writes data received to them. In other words it simply broadcasts the message to all the clients.

We use Usher to define routes for our application. And finally we start thin server to listen connections on port 8080.

Following is relevant javascript client side code in index.erb.

Lets try to understand it line by line.

Here we test if user's browser supports WebSockets. If it does not, we simply alert the user about it.

We create a room javascript object join function is called whenever a user clicks on "join" button on home page. It uses WebSocket API to create a new WebSocket instance that is tied to /socket end point in our application. onopen is called when the connection is successfully established between browser and server after a handshake. It simply updates some HTML fragments on our home page. onclose works on same lines however it is called when connection is terminated. onmessage is called whenever server pushes something to the browser. It simply updates the home page with message pushed by the server.

Following are some pointers that might be helpful exploring Cramp and WebSockets.