Cramp is a fully asynchronous real-time framework written by Pratik Naik, a developer at 37signals and a member of the Rails core-team. This framework is primarily intended for organizing bidirectional communication between the client and the server and has built-in support for WebSockets and Server-Sent Events. In this article we will discuss the main things related to the use of this tool, as well as try to understand its architecture and understand how it works.
Perhaps the main drawback of the framework is the small number of articles and examples, there is only a small page of documentation on the
official website and the actual
code of the project on the github . From there, basically everything written below was taken.
Architecture
Cramp is based on EventMachine, ActiveSupport and Thor and runs on a Thin or Rainbows server.
Thor is used to generate an application — Cramp :: Generators :: Application.start is called in the binary, which sequentially executes all the public methods of the Application class, whose names: create_root, create_config, create_models, speak for themselves.
')
Further, when the server starts, the EventMachine cycle starts, which catches all the events, launching the callbacks attached to them. That is why Cramp only works on Rainbows or Thin (by default), which are built on EM.
The key concept in the Cramp philosophy is Action (classes Action and Abstract). Action is somewhere between the controller and the actual action in Rails (if it is at all appropriate to compare asynchronous and synchronous frameworks). Each action processes one request or, it would be more correct to say, one entry point, because for example a connection through a web socket cannot be called a single request.
A typical query is conditionally divided into 4 stages:
- Initialization of the request - callbacks before_start are called.
At this stage, the headers have not yet been sent, you can redirect the user somewhere or interrupt the processing of the request. The documentation says that it is recommended to use before_start to check the request or user access rights. On before_start, you can hang several methods that will be executed sequentially, but each of them must call yield or halt to continue or terminate the processing of the request, respectively. - Initialization of the response - corresponds to the function respond_with.
It is used to form headers and usually use the standard header with the code 200:
def build_headers status, headers = respond_to?(:respond_with, true) ? respond_with.dup : [200, {'Content-Type' => 'text/html'}] headers['Connection'] ||= 'keep-alive' [status, headers] end
- The request is running - running on_start.
Here is the formation of the answer. On on_start, you can also hang several callbacks that will be executed at the same time , and each of them can call the render method and not just once. - Request completed - on_finish is called.
It is executed after the formation of the answer and serves to clean up something, close open connections, etc.
The killer feature of this framework is WebSockets. They are supported out of the box and do not require a separate server. To use web sockets it is enough to create an on_data handler method in your action. All other questions will be decided by the server implementing the receive_data EM method, which is triggered when a message is received from the connection and calls the user handler:
Thin
callback = @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK] callback.call(data) if callback
Rainbows
callback = @env[WEBSOCKET_RECEIVE_CALLBACK] callback.call(data) if callback
On it we will finish with architecture and we will pass to use.
Using
To work with Cramp, it is recommended to use Ruby branches 1.9 to use faybayra that with the presence of tools such as rvm or rbenv should not be a problem even if you are still sitting on 1.8.7.
Installing Cramp should not cause any difficulties:
gem install cramp
Next, run
cramp new project_name
and the aforementioned Thor generates the skeleton of your application.
Then, being in the project folder, we execute bundle install, which tightens all dependencies.
We have already created one action - app / actions / home_action.rb, with the following content:
class HomeAction < Cramp::Action def start render "Hello World!" finish end end
As it is easy to understand from this code, the start handler forms the answer in the form of the string “Hello World!” And finishes the processing of the request. Check this assumption by running the server:
bundle exec thin start
and indeed, everything works as expected. As far as I understand, Thin itself picks up the file config.ru, which lies in the project folder, but for fidelity you can run this command in the following form:
bundle exec thin --max-persistent-conns 1024 --timeout 0 -R config.ru start
or
bundle exec thin --max-persistent-conns 1024 --timeout 0 -V -R config.ru start
for debugging in verbose mode.
Naturally, we do not need to render static text and html markup directly, for this we must use the views (views) that are in separate files. Create an app / views folder and in it an index.erb file with the following simple content:
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> <link rel="stylesheet" type="text/css" href="stylesheets/styles.css" /> </head> <body> <h1>Hello World!</h1> </body> </html>
And in the public / stylesheets folder we create the styles.css file with just one line:
h1{color:red}
As you can see from the extension, in this example the Erb engine is used, but of course you can use Haml, Slim or whatever your heart desires, after having included the corresponding entry in the Gemfile.
In order for action to render the contents from the erb-file, first we will connect the engine itself:
require 'erb'
You can add this line to the action itself, but in order to use Erb in the whole project, it is more convenient to connect it to application.rb.
Next, change home_action.rb:
class HomeAction < Cramp::Action def start page = ERB.new(File.read(ExampleProject::Application.root('app/views/index.erb'))) render page.result(binding) finish end end
Here ExampleProject is the name of the project itself. But where is our css? In order for css-files to be available, you need to allow distribution of static content from the folder containing them in the config.ru file:
use Rack::Static, :urls => ["/javascripts", "/stylesheets"], :root => CrampArticle::Application.root(:public)
Well, the headline turned red, as expected.
Now let's go ahead and create a new action WsAction, which will communicate with the client via web sockets. First, let's establish which server the web sockets will work on:
Cramp::Websocket.backend = :thin
Again, instead of Thin, you can use Rainbows, I personally prefer Thin just because it lies on the Github and the docks on it are more.
Next, in the app / actions folder, create the ws_action.rb file and add the following code to it:
class WsAction < Cramp::Action self.transport = :websocket on_data :process_data def process_data(data) render "Hello" if /^hello/i =~ data end end
It is enough to add the line self.transport =: websocket to the class text so that it receives and sends messages via WebSockets. Then we set up a message handler and send a greeting if a message was received starting with “hello”.
To try this in action, first you need to add a route (route) for the WsAction class (config / routes.rb file):
get('socket/').to(WsAction)
And secondly, we add to client index.erb a code like this:
<script type="text/javascript"> var ws = new WebSocket("ws://localhost:3000/socket/"); ws.onopen = function() { ws.send("Hello!"); }; ws.onmessage = function(e) { alert(e.data); }; ws.onclose = function() { alert("closed"); }; </script>
For Firefox, the latest versions of WebSocket are replaced by MozWebSocket. The client code is very simple and is presented solely for demonstration (like the server one itself), therefore the condition for the web socket class or background on the flash is not included in it, do it yourself.
That's all, if you want to explore Cramp in more depth, there are a few more topics not covered in this article: Server-Side Events, filers, working with ActiveRecord. And of course you can help the development of the project with the help of bug reports and pull requests on the githaba.
Additional links
Project code
github.com/lifo/crampProject site (documentation, links to examples)
cramp.inWhat is EventMachine and how to use it
github.com/eventmachine/eventmachine/wikiWebsockets with Cramp
vinsol.com/blog/2011/05/09/websockets-with-crampAnother small article about Cramp and WebSockets
boldr.net/html5-websockets-cramp