Documentation
Installing Cramp is very straight forward.
$ gem install crampCramp 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/
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.
Every request in Cramp::Action goes through the following four states:
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.
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.
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.
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.
Cramp provides various callbacks for hooking into different states of the request.
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.
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
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 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
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.
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
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.
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.
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!
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.
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
You can use Active Record with Cramp by using mysql2 library’s em_mysq2 adapter and fibers.
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 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
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.