Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>First of all, Rack is exactly two things:</p> <ul> <li>A webserver interface convention</li> <li>A gem</li> </ul> <p><strong>Rack - The Webserver Interface</strong></p> <p>The very basics of rack is a simple convention. Every rack compliant webserver will always call a call method on an object you give him and serve the result of that method. Rack specifies exactly how this call method has to look like, and what it has to return. That's rack.</p> <p>Let's give it a simple try. I'll use WEBrick as rack compliant webserver, but any of them will do. Let's create a simple web application that returns a JSON string. For this we'll create a file called config.ru. The config.ru will automatically be called by the rack gem's command rackup which will simply run the contents of the config.ru in a rack-compliant webserver. So let's add the following to the config.ru file:</p> <pre><code>class JSONServer def call(env) [200, {"Content-Type" =&gt; "application/json"}, ['{ "message" : "Hello!" }']] end end map '/hello.json' do run JSONServer.new end </code></pre> <p>As the convention specifies our server has a method called call that accepts an environment hash and returns an array with the form [status, headers, body] for the webserver to serve. Let's try it out by simply calling rackup. A default rack compliant server, maybe WEBrick or Mongrel will start and immediately wait for requests to serve.</p> <pre><code>$ rackup [2012-02-19 22:39:26] INFO WEBrick 1.3.1 [2012-02-19 22:39:26] INFO ruby 1.9.3 (2012-01-17) [x86_64-darwin11.2.0] [2012-02-19 22:39:26] INFO WEBrick::HTTPServer#start: pid=16121 port=9292 </code></pre> <p>Let's test our new JSON server by either curling or visiting the url <code>http://localhost:9292/hello.json</code> and voila:</p> <pre><code>$ curl http://localhost:9292/hello.json { message: "Hello!" } </code></pre> <p>It works. Great! That's the basis for every web framework, be it Rails or Sinatra. At some point they implement a call method, work through all the framework code, and finally return a response in the typical [status, headers, body] form.</p> <p>In Ruby on Rails for example the rack requests hits the <code>ActionDispatch::Routing.Mapper</code> class which looks like this:</p> <pre><code>module ActionDispatch module Routing class Mapper ... def initialize(app, constraints, request) @app, @constraints, @request = app, constraints, request end def matches?(env) req = @request.new(env) ... return true end def call(env) matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' =&gt; 'pass'}, [] ] end ... end end </code></pre> <p>So basically Rails checks, dependent on the env hash if any route matches. If so it passes the env hash on to the application to compute the response, otherwise it immediately responds with a 404. So any webserver that is is compliant with the rack interface convention, is able to serve a fully blown Rails application.</p> <p><strong>Middleware</strong></p> <p>Rack also supports the creation of middleware layers. They basically intercept a request, do something with it and pass it on. This is very useful for versatile tasks.</p> <p>Let's say we want to add logging to our JSON server that also measures how long a request takes. We can simply create a middleware logger that does exactly this:</p> <pre><code>class RackLogger def initialize(app) @app = app end def call(env) @start = Time.now @status, @headers, @body = @app.call(env) @duration = ((Time.now - @start).to_f * 1000).round(2) puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms" [@status, @headers, @body] end end </code></pre> <p>When it gets created, it saves itself a copy of the actual rack application. In our case that's an instance of our JSONServer. Rack automatically calls the call method on the middleware and expects back a <code>[status, headers, body]</code> array, just like our JSONServer returns.</p> <p>So in this middleware, the start point is taken, then the actual call to the JSONServer is made with <code>@app.call(env)</code>, then the logger outputs the logging entry and finally returns the response as <code>[@status, @headers, @body]</code>.</p> <p>To make our little rackup.ru use this middleware, add a use RackLogger to it like this:</p> <pre><code>class JSONServer def call(env) [200, {"Content-Type" =&gt; "application/json"}, ['{ "message" : "Hello!" }']] end end class RackLogger def initialize(app) @app = app end def call(env) @start = Time.now @status, @headers, @body = @app.call(env) @duration = ((Time.now - @start).to_f * 1000).round(2) puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms" [@status, @headers, @body] end end use RackLogger map '/hello.json' do run JSONServer.new end </code></pre> <p>Restart the server and voila, it outputs a log on every request. Rack allows you to add multiple middlewares that are called in the order they are added. It's just a great way to add functionality without changing the core of the rack application.</p> <p><strong>Rack - The Gem</strong></p> <p>Although rack - first of all - is a convention it also is a gem that provides great functionality. One of them we already used for our JSON server, the rackup command. But there's more! The rack gem provides little applications for lots of use cases, like serving static files or even whole directories. Let's see how we serve a simple file, for example a very basic HTML file located at htmls/index.html:</p> <pre><code>&lt;!DOCTYPE HTML&gt; &lt;html&gt; &lt;head&gt; &lt;title&gt;The Index&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;p&gt;Index Page&lt;/p&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p>We maybe want to serve this file from the website root, so let's add the following to our config.ru:</p> <pre><code>map '/' do run Rack::File.new "htmls/index.html" end </code></pre> <p>If we visit <code>http://localhost:9292</code> we see our html file perfectly rendered. That's was easy, right?</p> <p>Let's add a whole directory of javascript files by creating some javascript files under /javascripts and adding the following to the config.ru:</p> <pre><code>map '/javascripts' do run Rack::Directory.new "javascripts" end </code></pre> <p>Restart the server and visit <code>http://localhost:9292/javascript</code> and you'll see a list of all javascript files you can include now straight from anywhere.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload