Introducing Cramp
Cramp is the latest entry on the ruby web frameworks list. However, unlike all the others, Cramp is an asynchronous framework, always running inside EventMachine reactor loop. Cramp isn’t a good fit for most of the web applications out there. However, Cramp is good at holding and working with a large number of open connections. Hence it’ll work great for things like comet, long polling, streaming API or even when your application needs to handle thousands of concurrent connections.
This article assumes that you’re aware with the evented programming model. If you are not, things below this point might not make much of a sense. If you’re interested in learning, you could start by reading about EventMachine and Twisted.
Install
As Cramp requires 0.2.0-pre Arel and 3.0-pre versions of ActiveSupport and ActiveModel. So you’ll have to install them first. This step will be irrelevant after Rails 3 gets released. But for now, the following should install them :
gem install arel --pregem install activemodel --preAnd then,
gem install crampThat’ll install Cramp gem along with all the needed dependencies. Please note that Cramp depends on ActiveSupport 3.0-pre gem, which isn’t backwards compatible and this may affect any other gems requiring ActiveSupport without specifying the version number.
Two faces of Cramp
Cramp comes with two layers : Controller & Model.
1) Cramp::Controller
Cramp::Controller is an asynchronous controller layer, that tries being rack compliant as much as possible. Currently, you must use thin or Rainbows! in order to run it. If you’re using Rainbows!, make sure you use EventMachine concurrency model.
Here’s the “hello world” of Cramp::Controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require 'rubygems' require 'cramp/controller' class WelcomeAction < Cramp::Controller::Action on_start :send_hello_world def send_hello_world render "Hello World" finish end end Rack::Handler::Thin.run WelcomeAction, :Port => 3000 |
Now run it directly :
ruby welcome_action.rbHere WelcomeAction is the rack endpoint, which you can use with config.ru or what have you.
Now let’s dig dipper to understand the code snippet above.
When serving a request, a Cramp::Controller::Action object goes through the following four stages :
- Initialization – This is the initial stage of an action when a request is received. During this stage, it’s possible to abort the request along with whatever headers/body you wish to send. Hence, it is typically useful for checking permissions and validating the request etc.
- Response Initialization – If the request doesn’t get aborted during the initialization stage, Cramp::Controller::Action enters the second stage, where response headers and a deferrable body are sent to the client. It’s important to note that as the headers are already sent here, following stages will only be able to send body and not able to change the headers.
- Starting – This is where the actual work happens. From this stage, you can send body to the clients as many times as you wish to or finish the request.
- Finishing – Cramp::Controller::Action enters this stage if the request is marked as finished during the Starting stage or if the client closes the connection. This stage is useful for cleanup activities or anything else you may wish to do upon request completion.
To hook into each of these states, Cramp::Controller::Action provides the following callbacks/methods :
before_start
before_start callback provides hook into the initialization stage. Each before_start callback accepts a block and must call yield ( or block.call – whichever is appropriate ) or halt. Here’s what a typical usage looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
before_start :verify_id, :find_user def verify_id if params[:id] !~ /\A\d+\z/ halt 500, {'Content-Type' => 'text/plain'}, "Invalid ID" else yield end end def find_user User.where(User[:id].eq(params[:id])).first do |user| if @user = user yield else halt 404, {}, "User not found" end end end |
As you can see above, halt takes status, headers and body as parameters and sends them to the client. It would also halt the callback chain and the request itself. Please note that on_finish callbacks will not get run when you halt. Calling yield will continue the filter chain or enter the next stage if no filters are left to run.
respond_with
After the before_start stage, Cramp::Controller::Action enters the next stage of Response Initialization. It’ll call the method respond_with, which must return an array of [status, headers]. If this method is not defined, [200, {‘Content-Type’ => ‘text/html’}] status and headers will be used by default.
Here’s an example of using respond_with to send custom status and headers :
1 2 3 4 |
def respond_with content_type = params[:format] == 'xml' ? 'application/xml' : 'application/json' [200, {'Content-Type' => content_type}] end |
A deferrable body is also sent out along with these headers.
on_start
After all the verifications and sending out headers, the real work starts. on_start callbacks provide multiple entry points into the Starting stage. These callbacks can send any body to the client using render() method or finish the request by invoking finish(). Note that the render() method can be called any number of times.
Here’s a full example imitating running two long running sql queries from on_start callbacks:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
require 'rubygems' require 'cramp/controller' require 'cramp/model' Cramp::Model.init(:username => 'root', :database => 'arel_development') class SleepingAction < Cramp::Controller::Action on_start :start_sleeping def start_sleeping Cramp::Model.select 'select sleep(1)', method(:on_first_sleep) end def on_first_sleep render "First Sleep Complete...\n" render "Going to sleep one more time now..\n" Cramp::Model.select 'select sleep(1)', method(:on_second_sleep) end def on_second_sleep render "Second Sleep Complete. Time to finish!\n" finish end end Rack::Handler::Thin.run SleepingAction, :Port => 3000 |
Now if you hit this using curl:
[lifo@null ~]$ curl http://0.0.0.0:3000/
First Sleep Complete...
Going to sleep one more time now..
Second Sleep Complete. Time to finish!
[lifo@null ~]$on_finish
on_finish provides hook into the last stage : Finishing. These callbacks are run when you call finish() from an on_start callback or when the client closes the connection. These callbacks are the perfect place for any cleaning up activities.
Here’s an example of a Cramp::Controller::Action using periodical timer and an on_finish callback.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
require 'rubygems' require 'cramp/controller' require 'cramp/model' class PollUserAction < Cramp::Controller::Action on_start :start_polling on_finish :stop_polling def start_polling # Call find_user every 10 seconds @timer = EventMachine::PeriodicTimer.new(2) { find_user } end def find_user User.first {|u| render "#{u.inspect}\n" } end def stop_polling puts "Cancelling the timer.." @timer.cancel end end Rack::Handler::Thin.run PollUserAction, :Port => 3000 |
In the case above, if you don’t call @timer.cancel, it’ll keep running even after the client closes the connection. The following section will cover the helper method provided by Cramp::Controller for the above pattern called periodic_timer.
Helper Methods
Periodic Timers
The on_finish callback example above has a very common pattern : Starting periodic timers using on_start and cleaning them up using on_finish. Cramp::Controller provides a better alternative : periodic_timer
Using periodic_timer to rewrite the example above :
1 2 3 4 5 6 7 |
class PollUserAction < Cramp::Controller::Action periodic_timer :poll_user, :every => 2 def poll_user User.first {|u| render "#{u.inspect}\n" } end end |
And then Cramp::Controller will take care of starting and stopping EventMachine::PeriodicTimer from the appropriate stages.
You can use more than one periodic timers as well :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class PollUserAction < Cramp::Controller::Action periodic_timer :poll_user, :every => 2 periodic_timer :check_limit_exceed, :every => 10 def poll_user .. end def check_limit_exceed finish if request_limit_exceeded end end |
In the example above, check_limit_exceed() calls finish() if the request limit is exceeded, which in turn will terminate the connection and stop all the timers too.
Keeping Connection Alive
If you’re using Cramp for streaming or long polling, you’d want to make sure the client doesn’t close the connection prematurely. Cramp::Controller has a handy helper method to make sure that doesn’t happen – keep_connection_alive
keep_connection_alive sends the client an empty string (” “) every 15 seconds by default.
1 2 3 4 |
class PollUserAction < Cramp::Controller::Action periodic_timer :poll_user, :every => 2 keep_connection_alive end |
Or you can change the period by supplying :every option :
1 2 3 4 |
class PollUserAction < Cramp::Controller::Action periodic_timer :poll_user, :every => 2 keep_connection_alive, :every => 30 end |
Routing, Request and Parameters
Cramp::Controller is capable of using a Rack middlewares for routing request and populating params. Here’s an example of a Cramp::Controller using Usher for routing :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
require 'rubygems' require 'cramp/controller' class HomeController < Cramp::Controller::Action on_start :send_hello_world def send_hello_world render "Hello World with #{params[:id]}" finish end end routes = Usher::Interface.for(:rack) do add('/:id').to(HomeController) end Rack::Handler::Thin.run routes, :Port => 3000 |
Cramp::Controller::Action implements the params method like:
1 2 3 |
def params @params ||= @env['usher.params'] end |
If you’re using another rack based router like rack-mount, you might want to override that with the appropriate definition.
As Cramp::Controller tries being a Rack based framework, you should be able to use Rails middlewares for cookies and sessions stores.
Views
Cramp::Controller does not have any built in mechanism for rendering files. However, using something like tilt should be very straight forward.
Testing
Cramp::Controller comes with a few helper methods to write integration tests for the application. However, that’s not within the scope of this article. You should have a read through http://github.com/lifo/cramp/tree/master/test/controller to know more.
Benchmarks
I haven’t really benchmarked Hello World for Cramp::Controller. However it should be very fast. I was able to have a single Cramp::Controller instance stream to 2000+ concurrent connections just fine. And this is where Cramp::Controller really shines, and not some silly Hello World masturbations.
Also, note that OS X isn’t the best environment for testing concurrent connections, you should be using Linux with a tuned Kernel for this. Check this article for details on tuning the Kernel.
Gotchas
Even though Cramp::Controller tries being as much Rack compliant as possible, it’s not a 100% Rack compliant framework. Rack specs are designed primarily for synchronous frameworks. As Cramp::Controller uses deferrable body, any middlewares operating on the response body will not work without modifications.
2) Cramp::Model
Cramp::Model is an asynchronous ORM ( only MySQL supported at the moment ) built on top of ARel and ActiveModel. It’s currently in a very primitive state and provides the following features :
- Validations using ActiveModel
- CRUD operations
- AR/ARel like Finder methods
Here’s what a Cramp model looks like :
1 2 3 4 5 6 7 8 9 10 11 |
require 'rubygems' require 'cramp/model' Cramp::Model.init(:username => 'root', :database => 'cramp_development') class User < Cramp::Model::Base attribute :id, :type => Integer, :primary_key => true attribute :name validates_presence_of :name end |
You are required to declare all the columns you wish to use as Attributes. If type is not supplied, String type is assumed.
Currently Cramp::Model provides the following finder method :
- all
- first
- each
It also provides the following methods for specifying options on the find :
- where
- select
- group
- order
- limit
- offset
The above methods are delegated to Arel and can be chained. As Cramp::Model is an asynchronous ORM, you must supply a callback method for processing the result records. You could do that by calling all, first or each at the end of the chain and supply the callback as a block or a method.
Here are some example usages :
1 2 3 4 5 6 7 8 9 10 11 |
EM.run do User.select(:id, :name).limit(10).offset(20).first {|u| .. } User.where(User[:name].eq('Lush')).limit(2).all {|users| ... } User.each {|user| .. } def lush_users_found(users) ... end User.where(User[:name].eq('Lush'), User[:id].gt(5)).all method(:lush_users_found) end |
For the basic CRUD operations, Cramp::Model provides methods similar to Model#save, which accepts an optional callback. If a callback is provided, it’ll be called after the completion with a Status object, containing metadata about the success or failure of the save operation. Status object has just two methods defined on it : record & success?.
Here’s how you would typically want to save a record to the database :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
EM.run do def user_saved(status) if status.success? ... else user = status.record puts "Oops. Found errors : #{status.record.errors.inspect}" end end user = User.new user.save method(:user_saved) end |
Contributing to Cramp
Cramp is hosted on Github http://github.com/lifo/cramp. Please do fork and use the issues if you encounter a bug or need any kind of help.
UPDATE 1 : Add instructions to install arel 0.2.0-pre
UPDATE 2 : Rainbows! work with cramp!


Recent comments
1 week 1 day ago
1 week 2 days ago
3 weeks 1 day ago
5 weeks 2 days ago
20 weeks 20 hours ago
23 weeks 23 hours ago
23 weeks 6 days ago
23 weeks 6 days ago
24 weeks 1 day ago
26 weeks 2 days ago