Documentation

  1. Installation
  2. Getting Started
  3. Lifecycle of a Request
  4. Callbacks
  5. Convenience Methods
  6. Real-time HTML5
  7. Pseudo-Synchronous Callbacks with Fibers
  8. Integration with External Libraries

1. Installation

Ruby 1.9.2+ is recommended for use with Cramp

Installing Cramp is very straight forward.

$ gem install cramp

2. Getting Started

Cramp ships with a generator script for creating a barebone application.

$ cramp new chat
      create  
      create  config.ru
      create  Gemfile
      create  application.rb
      create  public
      create  public/javascripts
      create  config
      create  config/routes.rb
      create  app/actions
      create  app/actions/home_action.rb

Before you could run the generated Cramp application, you need to install all the dependencies:

$ cd chat/
$ bundle install

And then start the server:

$ bundle exec thin --timeout 0 -R config.ru start

You should be greeted with “Hello World” on browsing to http://0.0.0.0:3000/

Architecture Overview

A Rails application is typically divided into multiple controllers, with each controller containing multiple actions that corresponds to different URIs. Cramp has no concept of “controllers”. Cramp has Cramp::Action, which is equivalent of a single action in Rails and typically corresponds to a single entry point.

In Cramp, each request goes through four different states during its lifecycle: Request Initialization, Response Initialization, Started Request, Finished Request. Cramp provides various callbacks and helper methods to hook into each of these states. You can find out more about the purpose of these states in the Lifecycle of a Request section.

Cramp::Action is the primary interface provided by Cramp. Here’s a very simple cramp action:

# app/actions/home_action.rb
class HomeAction < Cramp::Action
  def start
    render "Hello World!"

    # Do more stuff

    render "Hello World Again!"
    finish
  end
end

This example demonstrates a hook into the “Started Request” state. As you can see, it explicitly calls finish, which is to terminate the request. All cramp actions always have to call finish to terminate the request.

Calling render immediately flushes the supplied data to the client without closing the connection. You can call render any number of times, before terminating the request with finish.

3. Lifecycle of a Request

Every request in Cramp::Action goes through the following four states:

Request Initialization

This is the initial state of an action when the request is first received. During this state, 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 state, Cramp::Action enters the second state, 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 states will only be able to send body and not able to change the headers.

Started Request

This is where the actual work happens. From this state, you can send body to the clients as many times as you wish to or finish the request.

Finished Request

Cramp::Action enters this state if the request is marked as finished during the Starting state or if the client closes the connection. This state is useful for cleanup activities or anything else you may wish to do upon request completion.

4. Callbacks

Cramp provides various callbacks for hooking into different states of the request.

before_start

before_start callback provides a hook for the Request 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:

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
  if @user = User.find(params[:id])
    yield
  else
    halt 404, {}, "User not found"
  end
end

Cramp executes before filters sequentially, in the same order they are defined. Each before filter can either stop the filter chain and terminate the request or continue to the next before filter or the next request state:

  • halt(http_status, http_headers, body) – Calling halt sends the supplied status/headers/body to the client and also terminates the request.
  • yield – Calling yield will continue the filter chain or enter the next state when there are no before filters left to be run.

Each before_filter must call either halt or yield.

respond_with

When all before filters are done running, the request enters the next state – Response Initialization. In this state, Cramp sends response headers to the client by calling respond_with method.

Here’s the default definition of respond_with:

def respond_with
  [200, {'Content-Type' => 'text/html'}]
end

You can override respond_with in your Cramp::Action if you wish to send a different HTTP status or headers. respond_with must return an array of [http_status, http_headers].

Here’s an example of using respond_with to send custom status and headers:

def respond_with
  content_type = params[:format] == 'xml' ? 'application/xml' : 'application/json'
  [200, {'Content-Type' => content_type}]
end

on_start

After all the verifications and sending out headers, the real work starts. on_start callbacks provide multiple entry points into the Started Request state. These callbacks can send response body to the client by calling render. render streams the supplied body to the client, without closing the connection.

render method can be called any number of times.

Alternatively, on_start callbacks can terminate the request by invoking finish.

Here’s a hypothetical example that has two entry points into the “Started Request” state: Subscribing to Redis and fetching tweets. These callbacks will be run simultaneously.

class ChatAction < Cramp::Action
  on_start :setup_redis_callbacks
  on_start :fetch_twitter

  def setup_redis_callbacks
    @redis = MagicalRedis.subscribe('chat', method(:on_data_from_redis))
  end

  def fetch_twitter
    MagicalTwitter.fetch('lifo') do |tweets|
      render tweets.to_json
    end
  end

  protected

  def on_data_from_redis(data)
    body = {:chat => data}.to_json
    render body
  end
end

on_finish

on_finish callbacks provide hooks into the last state of the request: Finished Request. These callbacks are run when you call finish from an on_start callback or when the client closes the connection. These callbacks provide the perfect place for cleaning things up upon request termination.

In the above example of the on_start callbacks, it’s subscribing to a Redis channel. Now let’s close that connection when the request terminates:

class ChatAction < Cramp::Action
  on_start :setup_redis_callbacks
  on_finish :close_redis_connection

  def setup_redis_callbacks
    @redis = MagicalRedis.subscribe('chat', method(:on_data_from_redis))
  end

  def close_redis_connection
    @redis.close
  end
end

5. Convenience Methods

Periodic Timers

It is a very common pattern to start EventMachine periodic timers using on_start and cleaning them up using on_finish. Cramp encapsulates this behaviour with periodic_timer:

class PollUserAction < Cramp::Action
  on_start :find_last_user
  periodic_timer :poll_new_user, :every => 2

  def find_last_user
    @user = User.last
  end

  def poll_new_user
    last_user = User.last

    if @user != last_user
      render "New user created! #{@user.inspect}"
    end 
  end

end

In the above example, poll_new_user method will get called every 2 seconds.

You can use as many periodic_timer as you wish:

class PollUserAction < Cramp::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

Here check_limit_exceed calls finish if the request limit rate is exceeded, which in turn will terminate the request 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 when there is no response for some time. Cramp 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.

class PollUserAction < Cramp::Action
  periodic_timer :poll_user, :every => 2
  keep_connection_alive
end

You can change the default period by supplying :every option:

class PollUserAction < Cramp::Action
  periodic_timer :poll_user, :every => 2
  keep_connection_alive :every => 30
end

Routing, Request and Parameters

Cramp integrates with http_router for routing and parsing parameters. This is accessible with params method. However, this is entirely optional and you can use any other mechanism that you like and override the default params method.

Cramp::Action also has a request method that returns a Rack::Request object.

6. Real-time HTML5

WebSockets

Cramp has WebSockets support baked right in! As Cramp extends the underlying webserver ( Thin/Rainbows! ) to add the WebSockets superpowers, you don’t have to run any other service for working with WebSockets.

First, you must explicitly enable the WebSockets support:

Cramp::Websocket.backend = :thin # or :rainbows

Then you have to specify WebSocket transport in the action:

class EchoAction < Cramp::Action
  self.transport = :websocket
end

Cramp provides on_data callbacks for receiving data over the WebSocket.

on_data :message_received

def message_received(data)
  Message.create!(data)
  render 'ok'
end

Here’s a complete example that echoes everything received over the WebSocket back to the client over the same WebSocket:

Cramp::Websocket.backend = :thin

class EchoAction < Cramp::Action
  self.transport = :websocket

  on_data :received_data

  def received_data(data)
    render "Got your #{data}"
  end
end

You can very easily fallback to flash sockets if the browser doesn’t have WebSockets support. Check out the web-socket-js library by Hiroshi Ichikawa, which does exactly that. Cramp will “just work” with the web-socket-js library.

Server-Sent Events ( EventSource )

WebSockets are an overkill when you don’t need to send any data from the client to the server in real-time. That’s where Server-Sent Events enter the picture. If you have never heard of Server-Sent Events before, here’s a good tutorial. You should also check out this case study comparing WebSockets and Server-Sent Events.

To work with Server-Sent Events, all you have to do is specify it as the transport in your action:

class TimeAction < Cramp::Action
  self.transport = :sse
end

Here’s a complete example that sends back the server time when the client initially connects. And every 2 seconds after that.

class TimeAction < Cramp::Action
  self.transport = :sse

  on_start :send_latest_time
  periodic_timer :send_latest_time, :every => 2

  def send_latest_time
    data = {'time' => Time.now.to_i}.to_json
    render data
  end
end

When the browser doesn’t support EventSource, you can easily fallback to long polling. Check out polyfills and jquery.eventsource libraries providing pure javascript based fallbacks for EventSource. No flash!

7. Pseudo-Synchronous Callbacks with Fibers

In Ruby 1.9, you can use fibers to get rid of evented IO callbacks and emulate synchronous execution. Please check out Ilya Grigorik’s Untangling Evented Code with Ruby Fibers article to understand the how.

Cramp lets you wrap each callback and timer in its own fiber.

Cramp wraps individual callbacks in a fiber, rather than wrapping the entire request in a single fiber. This results in a much better resource utilization.
class TimeAction < Cramp::Action
  use_fiber_pool
end

By default, this will create a pool of 100 fibers. If you want specify a different number:

class TimeAction < Cramp::Action
  use_fiber_pool :size => 1000
end

You can also register callbacks to run after each fiber is done executing a callback. For example, you may want to release database connection held by the fiber on completion:

class FibersAction < Cramp::Action
  use_fiber_pool(:size => 1000) do |pool|
    pool.generic_callbacks << proc { ActiveRecord::Base.clear_active_connections! }
  end
end

8. Integration with External Libraries

Active Record

You can use Active Record with Cramp by using mysql2 library’s em_mysq2 adapter and fibers.

Be sure to set the Active Record connection pool size same as that of the fiber pool.

To create a new Cramp application that uses Active Record, pass -M option to the generator:

$ cramp new chat -M
      create  
      create  config.ru
      create  Gemfile
      create  application.rb
      create  public
      create  public/javascripts
      create  config
      create  config/routes.rb
      create  config/database.yml
      create  app/actions
      create  app/actions/home_action.rb
      create  app/models

EM-Synchrony

EM-Synchrony should “just work” with Cramp when you’re using fibers.

Here’s an example that fetches http://m.onkey.org and renders the response back to the user. All asynchronously!

require 'em-http-request'
require 'em-synchrony/em-http'

class SynchronyAction < Cramp::Action
  use_fiber_pool

  def start
    page = EventMachine::HttpRequest.new("http://m.onkey.org").get
    render page.response
    finish
  end
end

Rack Middlewares

As Cramp is an asynchronous framework, you cannot use any middlewares that operate on the response body and not prepared to operate on an asynchronous body.

Konstantin Haase’s excellent async-rack provides async-proof versions of all the middlewares that ship with the Rack gem.