LakTEK A Sri Lankan, A Rubyist and A Web Dude

Posted
16 February 2010 @ 12pm

Tagged
, , , ,

Building Real-time web apps with Rails3

On deciding the web framework to build Realie, one of the main considerations was should I move to a totally asynchronous framework? Most established web frameworks, including my favorite Rails is built in a synchronous manner and follows a call-stack based model. Real-time web apps needs to be asynchronous. Evented programming model ideally suits to this.

Since there were lot of hype around Node.js based async web frameworks in last couple of months, my initial idea was also to use such framework for my project. However, that felt as a totally new learning curve. Apart from grasping how to use JavaScript in server-side, it also meant I need to adopt to a totally new eco-system of templating, routing and etc.

However, when I revisit my requirement it was clear only a part of the web app really needs to be asynchronous. Most parts can still be done with a traditional call stack based web framework. Using a fully async. web framework to build an entire app seems to be useless. Also, it felt an overkill to run two different apps to serve sync and async stuff.

In this context, I came to know about Cramp, a Ruby asynchronous framework written by Pratik Naik. Best thing about Cramp is capability of using Rack middlewares (keep in mind not fully Rack compliant). Then came the option how about using Rails and Cramp together to build a hybrid of real-time web app. With Rails3, it makes it so easy to mix any other Rack endpoint with Rails. So this sounded a perfect solution to my problem.

Since, Cramp follows evented model it needs an evented web server such as Thin or Rainbows!. Further, Cramp has implemented websockets support for these two server backends.

Integrating Rails3 and Cramp

First of all, you will need to bundle Cramp gem, with your Rails app. For this, open the `Gemfile` and add the following:

    gem "cramp", :require => 'cramp/controller'

For my work I only needed the Cramp controller (it has also got an async model) so for now I only required it.

As I mentioned earlier, to support web sockets, cramp needs to extend the web server we use. To specify the web server, I added an initializer (`config/initializers/cramp_server.rb`) with the following line :

    Cramp::Controller::Websocket.backend = :thin

Then, I created a simple Cramp controller, which can respond to web sockets (`app/cramps/communications_controller.rb`)

class CommunicationsController < Cramp::Controller::Websocket
  periodic_timer :send_hello_world, :every => 2
  on_data :received_data
 
  def received_data(data)
    if data =~ /stop/
      render "You stopped the process"
      finish
    else
      render "Got your #{data}"
    end
  end
 
  def send_hello_world
    render "Hello from the Server!"
  end
end

Now the fun part! The new Rails3 router supports pointing to any Rack compatible endpoint, so we can easily hook our cramp controller for public access. In `config/routes.rb` add the following:

  match "/communicate", :to => CommunicationsController

Our Cramp endpoint can co-exist with rest of the Rails controllers without any issues.

Viola!

Another important change with Rails3 is its also a fully compatible Rack app now. This means as any other Rack app, we can also start our Rails app by running `rackup`.

rackup -s thin -p 3000 --env production

This will start our app using Thin server backend on port 3000. Keep in mind, we need to provide an environment other than `development`, to avoid Rack Lint middleware. This is because Cramp is not a fully compatible with Rack SPEC and it will throw exceptions.


  • Igor Wiedler
    I've been trying to get cramp working with rails3 routes, but I keep getting the following exception:

    undefined method `action' for SocketController:Class

    SocketController naturally extends Cramp::Controller::Websocket. I'm pretty certain I followed all instructions correctly. The rails 3 controllers are being served through rack/thin just fine. Any hints would be great.
  • I've written a similar blog post (I didn't publish it) and the problem is that if you're mixing sync requests with async requests, the sync requests will block the async requests on the thin instance. So in this situation Cramp will be useless (or not scalable). Either you have 2 separate Rack apps or 2*n thin instances (one for Rails, one for Cramp).
  • laktek
    I agree with you on this and actually I looked at the issue only from the development perspective. Though, I haven't actually researched much on the production environment of the app, it seems using a load balancer to direct sync and async requests to two separate Thin instances would be good enough.
  • nickL
    This is interesting. But how could that be done via Load Balancer? I've explored Cramp however i'm concerned as Nicolas mentioned that scaling it with a Rails3 app may prove difficult. Ideas?
  • Interesting post. What's the rest of the stack? Apache and passenger? In which case - how well does it handle long lived connections.

    You might be interested in my work on Juggernaut http://juggernaut.rubyforge.org (old project) and a new project Syncro: http://github.com/maccman/syncro
  • laktek
    Passenger doesn't really support evented model, so we need to stick with Thin or Rainbows! as the application server. As I mentioned below comment, we will need to have a web server with load balancing capabilities (nginx would also be suitable).

    Thanks for pointing at Juggernaut and Syncro. I have actually read about Juggernaut sometime back. Syncro looks even more interesting, wil take some time to dig deeper on it.
blog comments powered by Disqus