A Quick Introduction to Rack

In this article, we will cover the following:

Introduction: What’s Rack?

In the words of the author of Rack – Christian Neukirchen: Rack aims to provide a minimal API, to develop web applications in Ruby, for connecting web servers supporting Ruby (like WEBrick, Mongrel etc.) and Ruby web frameworks (like Rails, Sinatra etc.).

Web frameworks such as Sinatra are built on top of Rack or have a Rack interface for allowing web application servers to connect to them.

Rack includes handlers that connect Rack to all the web application servers supporting Ruby (WEBrick, Mongrel etc.).

Rack includes adapters that connect Rack to various Ruby web frameworks (Sinatra, Rails etc.).

Between the server and the framework, Rack can be customized to your applications needs using middleware. The fundamental idea behind Rack middleware is – come between the calling client and the server, process the HTTP request before sending it to the server, and processing the HTTP response before returning it to the client.

Rack App
A Rack App

Rubyist Matt Aimonetti has this to say about Rack:

Rack is the foundation of a great majority of modern Ruby web frameworks. This common interface between web servers and web applications is critical to understand since as a Ruby web developer you will more than likely have to deal with Rack middleware and/or Rack applications.

Rails for instance is built on top of Rack and it allows you to add or remove middleware as well as mount full Rack applications within your Rails app. Sinatra on the other hand is basically a library of helpers for Rack, making the development of Rack applications easier and more consistent while still being very flexible.

The good news is that the power of Rack is in its simplicity and therefore even someone who just started with Ruby should be able to quickly grasp its core concepts.

The premise of Rack is simple – it just allows you to easily deal with HTTP requests.

HTTP is a simple protocol: it basically describes the activity of a client sending a HTTP request to a server and the server returning a HTTP response. Both HTTP request and HTTP response in turn have very similar structures. A HTTP request is a triplet consisting of a method and resource pair, a set of headers and an optional body while a HTTP response is in triplet consisting of a response code, a set of headers and an optional body.

Rack maps closely to this. A Rack application is a Ruby object (not a class) that has a call method, which has a single argument, the environment. The environment must be a true instance of Hash. This hash contains all the relevant information about the request: this includes the HTTP verb used by the request, the path that is requested, the headers that have been sent by the client, and so on. The app should return an Array of exactly three values: the status code (it must be greater than or equal to 100), the headers (must be a hash), and the body (the body commonly is an Array of Strings, the application instance itself, or a File-like object. The body (corresponding to a HTTP response) must respond to method each and must only yield String values.)

A simple Rack app, without the rack gem

A quick visit to Ruby’s proc object

Remember the proc object from Ruby? Blocks are not objects, but they can be converted into objects of class Proc. This can be done by calling the lambda method of the Kernel module which is included in the Object class. A block created with lambda acts like a Ruby method. The class Proc has a method call that invokes the block.

In irb type:

>> my_rack_proc = lambda {puts 'Rack Intro'}
=> #<Proc:0x1fc9038@(irb):2(lambda)>
>> # method call invokes the block
?> my_rack_proc.call
Rack Intro
=> nil
>>

The Rack app – my_rack_proc

As mentioned earlier, our simple Rack application is a Ruby object (not a class) that responds to call and takes exactly one argument, the environment. The environment must be a true instance of Hash. The app should return an Array of exactly three values: the status code (it must be greater than or equal to 100), the headers (must be a hash), and the body (the body commonly is an Array of Strings, the application instance itself, or a File-like object. The body must respond to method each and must only yield String values.) That’s the Rack specification in a nutshell. Strictly speaking, you don’t need the rack gem in order to write Rack
ready applications
. Just stick to the specification and that’s it.

Let us create our new proc object. Type:

>> my_rack_proc = lambda { |env| [200, {}, ["Hello. The time is #{Time.now}"]] }
=> #<Proc:0x1f4c358@(irb):5(lambda)>
>>

Now we can call the proc object my_rack_proc with the call method. Type:

>> my_rack_proc.call({})
=> [200, {}, ["Hello. The time is 2011-10-24 09:18:56 +0530"]]
>>

my_rack_proc is our single line Rack application.

In the above example, we have used an empty hash for headers. Instead, let’s have something in the header as follows:

>> my_rack_proc = lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
=> #<Proc:0x1f4c358@(irb):5(lambda)>
>>

Now we can call the proc object my_rack_proc with the call method. Type:

>> my_rack_proc.call({})
=> [200, {"Content-Type" => "text/plain"}, ["Hello. The time is 2011-10-24 09:18:56 +0530"]]
>>

Using the Rack gem

Installation

The rack gem is a collection of utilities and facilitating classes, to make life easier for anyone developing Rack applications. It includes basic implementations of request, response, cookies, sessions and a good number of useful middlewares.

Note that I have Ruby 2.0 installed on a Windows box and all the programs in this article have been tested using that.

Let’s check if we already have rack with us. In the already open irb window, type:

>> require 'rack'
=> true
>>

Yes, rack’s already there on your machine. If rack’s not there you will get an error like:

LoadError: no such file to load -- rack

You can install rack by opening a new command window and typing:

$ gem install rack

Rack Handlers

We can run our previously written Rack application (my_rack_proc) with any of the Rack handlers.

To look at the Rack handlers available, in the already open irb window, type:

>> require 'rack'
=> true
>> Rack::Handler.constants
=> [:CGI, :FastCGI, :Mongrel, :EventedMongrel, :SwiftipliedMongrel, :WEBrick, :LSWS, :SCGI, :Thin]
>>

If you look at the code of the Rack program handler.rb, you will notice that in the method def self.default(options = {}), Rack uses Thin by default if available, then Puma and then WEBrick.

To get a handler for say WEBrick (WEBrick, web application server, comes along with Ruby), type:

>> Rack::Handler::WEBrick
=> Rack::Handler::WEBrick
>>

All of these handlers have a common method called run to run all the Rack based applications.

>> Rack::Handler::WEBrick.run my_rack_proc
[2011-10-24 10:00:45] INFO WEBrick 1.3.1
[2011-10-24 10:00:45] INFO ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 10:00:45] INFO WEBrick::HTTPServer#start: pid=1788 port=80

Open a browser window and type the url: http://localhost/

In your browser window, you should see a string, something like this:

Hello. The time is 2011-10-24 10:02:20 +0530

Note: If you already have something running at port 80, you can run this app at a different port, say 9876. In the open irb window, press Ctrl-C to stop the WEBrick server. Next type:

>> Rack::Handler::WEBrick.run my_rack_proc, :Port => 9876
[2011-10-24 11:32:21] INFO  WEBrick 1.3.1
[2011-10-24 11:32:21] INFO  ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 11:32:21] INFO  WEBrick::HTTPServer#start: pid=480 port=9876

Open a browser window and type the url: http://localhost:9876/

In your browser window, you should see a string, something like this:

Hello. The time is 2011-10-24 10:02:20 +0530

Another Rack app – my_method

A Rack app need not be a lambda; it could be a method. In the open irb window, press Ctrl-C to stop the WEBrick server. Next type:

>> def my_method env
>> [200, {}, ["method called"]]
>> end
=> nil

We declare a method my_method that takes an argument env. The method returns three values.

Next type:

>> Rack::Handler::WEBrick.run method(:my_method)
[2011-10-24 14:32:05] INFO  WEBrick 1.3.1
[2011-10-24 14:32:05] INFO  ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 14:32:05] INFO  WEBrick::HTTPServer#start: pid=1644 port=80

Open a browser window and type the url: http://localhost/

In your browser window, you should see something like this:

method called

Method objects are created by Object#method. They are associated with a particular object (not just with a class). They may be used to invoke the method within the object. The Method.call method invokes the method with the specified arguments, returning the method’s return value.

Press Ctrl-C in irb to stop the WEBrick server and then close the irb window.

Using rackup

The rack gem comes with a bunch of useful stuff to make life easier for a rack application developer. rackup is one of them.

rackup is a useful tool for running Rack applications. rackup automatically figures out the environment it is run in, and runs your application as FastCGI, CGI, or standalone with Mongrel or WEBrick – all from the same configuration.

To use rackup, you’ll need to supply it with a rackup config file. By convention, you should use .ru extension for a rackup config file. Supply it a run RackObject and you’re ready to go:

By default, rackup will start a server on port 9292.

To view rackup help, in the already open command window type:

$ rackup --help

Let us create a config.ru file that contains the following:

run lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }

This file contains run, which can be called on anything that responds to a .call.

To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru
[2011-10-24 15:18:03] INFO WEBrick 1.3.1
[2011-10-24 15:18:03] INFO ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 15:18:03] INFO WEBrick::HTTPServer#start: pid=3304 port=9292

Open a browser window and type the url: http://localhost:9292/

In your browser window, you should see something like this:

Hello. The time is 2011-10-24 15:18:10 +0530

Press Ctrl-C to stop the WEBrick server.

Now let’s move our application from the config.ru file to my_app.rb file as follows:

# my_app.rb
class MyApp
  def call env
    [200, {"Content-Type" => "text/html"}, ["Hello Rack Participants"]] 
  end
end

Also, our config.ru will change to:

require './my_app'
run MyApp.new

To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru
[2011-10-25 06:18:16] INFO  WEBrick 1.3.1
[2011-10-25 06:18:16] INFO  ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-25 06:18:16] INFO  WEBrick::HTTPServer#start: pid=2224 port=9292

Open a browser window and type the url: http://localhost:9292/

In your browser window, you should see something like this:

Hello Rack Participants

Press Ctrl-C to stop the WEBrick server.

Note: Ctrl C may not work in some distributions. In such cases, you can start an app with the WEBrick handler as follows:

Rack::Server.new( { :app => my_rack_proc, :server => 'webrick', :Port => 9876} ).start

You can find more details on Rack::Server here.

Deploying Pure Rack Apps to Heroku

If you don’t have Git on your computer, then you need to download and install Git. The precompiled packages are available here: http://git.or.cz/

Select the relevant package for your operating system.

For my Windows box, I installed by running the EXE installer. Accept the default install directory. When you get to the “Select Components” settings screen, it is recommended to use the “Simple context menu | Git Bash Here” option. In the “Adjusting your PATH environment” screen, it is recommended to use the “Use Git Bash only” option. Use other default options.

Please ensure that you are connected to the internet and then create an account on Heroku (obviously do this only once) if you don’t have one – http://heroku.com/signup.

Open a new command window to install the Heroku gem file. Type:

gem install heroku

Next, create a new folder, say rackheroku. Assuming that you have Git installed, open a Bash shell in that folder. You now need to identify yourself to Git (you need to do this only once). With the bash shell still open type in the following:

git config --global user.name "Your name here"
git config --global user.email "Your email id"

Git does not allow accented characters in user name. This will set the info stored when you commit to a Git repository. Git has now been set up.

The first step in using Git is to create your SSH Key. This will be used to secure communications between your machine and other machines, and to identify your source code changes. (If you already have an SSH key for other reasons, you can use it here, there is nothing Git-specific about this.)

To create our ssh key, open a new command window and type:

$ ssh-keygen -C "username@email.com" -t rsa

(with your own email address, of course).

Accept the default key file location. When prompted for a passphrase, make one up and enter it. If you feel confident that your own machine is secure, you can use a blank passphrase, for more convenience and less security. Note where it told you it stored the file. On my Windows box, it was stored in “C:\Documents and Settings\A\.ssh\”. Memorize your passphrase carefully. If you forget it, you will NOT be able to recover it.

Open the public file id_rsa.pub with a text editor. The text in there is your “public SSH key”.

Upload your public key (do it only once):

$ heroku keys:add

You’ll be prompted for your username and password the first time you run a heroku command; they’ll be saved on ~/.heroku/credentials so you won’t be prompted on future runs. It will also upload your public key to allow you to push and pull code.

In order for Heroku to know what to do with your Rack app, create a config.ru (ru stands for Rack up) in the rackheroku folder. The contents are:

require './my_app'
run MyApp.new

Also, copy the my_app.rb file to the rackheroku folder. It’s contents are:

# my_app.rb
class MyApp
  def call env
    [200, {"Content-Type" => "text/html"}, ["Hello Rack Participants"]] 
  end
end

In the already open command window we will install bundler, if you don’t have it already. Type:

gem install bundler

Close the command window. Next, we will install the required gems (if any) via bundler. In the already open Git Bash shell for folder rackheroku type:

$ bundle init
Writing new Gemfile to c:/rackheroku/Gemfile

Edit the created Gemfile with your preferred text editor to let it look like this:

source "http://rubygems.org"
gem 'rack'

Now we need to tell Bundler to check if we’re missing the gems our application depends on, if so, tell it to install them. In your open Bash shell type:

$ bundle check
The Gemfile's dependencies are satisfied

Finally in the open Bash shell, type:

$ bundle install

This will ensure all gems specified on Gemfile, together with their dependencies, are available for your application. Running bundle install will also generate a Gemfile.lock file. The Gemfile.lock ensures that your deployed versions of gems on Heroku match the version installed locally on your development machine.

Next we set up our local app to use Git. In the already open Git Bash shell for folder rackheroku type:

$ git init
$ git add .
$ git commit -m "Rack app first commit"

Let’s create our Rack app on Heroku. Next type:

$ heroku create
Creating quiet-winter-3741... done, stack is bamboo-mri-1.9.2
http://quiet-winter-3741.heroku.com/ | git@heroku.com:quiet-winter-3741.git
Git remote heroku added

The app has been created and two URLs are provided. One is for the web face of your new app i.e. http://quiet-winter-3741.heroku.com/ If you visit that URL now, you’ll see a standard welcome page, until you push your application up. The other one is for the Git repository that you will push your code to. Normally you would need to add this as a git remote; the heroku create command has done this for you automatically. Do note that the output from the create command will be different for each one of you.

Now let us deploy our code to Heroku. Next type:

git push heroku master

At this stage we can rename our app to rackheroku. Now type:

$ heroku rename rackheroku
http://rackheroku.heroku.com/ | git@heroku.com:rackheroku.git
Git remote heroku updated

Our app is now deployed to Heroku. Open a new browser window and type http://rackheroku.heroku.com/

In the browser window, you should see:

Hello Rack Participants from across the globe

Using Rack::Request and Rack::Response

If you want to develop outside of existing frameworks, implement your own ones, or develop middleware, Rack provides many helpers to create Rack applications quickly and without doing the same web stuff all over:

  • Rack::Request, which also provides query string parsing and multipart handling.
  • Rack::Response, for convenient generation of HTTP replies and cookie handling.

We will use cURL, Rack::Request and Rack::Response in the code examples that follow.

First, let’s look at the following cURL command:

curl -X request

(HTTP) Specifies a custom request (GET, POST) to use when communicating with the HTTP server. The specified request will be used instead of the standard GET.

curl -X POST -d data

(HTTP) Sends the specified data in a POST request to the HTTP server, in a way that can emulate as if a user has filled in a HTML form and pressed the submit button. Note that the data is sent exactly as specified with no extra processing (with all newlines cut off). The data is expected to be “url-encoded”. This will cause cURL to pass the data to the server using the content-type application/x-www-formurlencoded. If more than one -d/–data option is used on the same command line, the data pieces specified will be merged together with a separating &-letter. Thus, using ‘-d name=daniel -d skill=lousy’ would generate a post chunk that looks like
‘name=daniel&skill=lousy’.

If you start the data with the letter @, the rest should be a file name to read the data from, or – if you want cURL to read the data from stdin. The contents of the file must already be url-encoded.

Let us write a program to explore this:

Our config.ru will be:

require './my_request'
run MyRequest.new

Our application my_request.rb is as follows:

class MyRequest
  def call(env)
    req = Rack::Request.new(env)
    name = req.params['name']
    text = req.params['text']
    Rack::Response.new.finish do |res|
      res['Content-Type'] = 'text/plain'
      res.status = 200
      str = "Parameters sent: name - #{name} | text - #{text}"
      res.write str
    end
  end
end

To run our rack app, in the same folder that contains config.ru, open a command window and type:

$ rackup config.ru

Our client is cURL. Open a Bash shell and type:

$ curl -d "name=Satish%20Talim&text=This%20course%20is%20awesome!" localhost:9292

The post data must be urlencoded. Now, in your Bash shell, you should see something like this:

Parameters sent: name - Satish Talim | text - This course is awesome!

Type exit in your Bash shell and press Ctrl-C in the command window to stop the WEBrick server. Close the command window.

A very basic practical Rack app

Credit: Matt Aimonetti

Matt has been kind enough to write this app for us. It’s a very basic rack application showing how to use a router based on the uri and how to process requests based on the HTTP method used. The explanation is here.

Another practical Rack app

Credit: Konstantin Haase

Konstantin has provided us with another practical Rack app, where we use cURL to upload a text file and which then gets sorted.

Using Rack middleware

We mentioned earlier that between the server and the framework, Rack can be customized to your applications needs using middleware. The fundamental idea behind Rack middleware is – come between the calling client and the server, process the HTTP request before sending it to the server, and processing the HTTP response before returning it to the client.

Rackup also has a use method that accepts a middleware. Let us use one of Rack’s built-in middleware.

You must have observed that every time we make a change to our config.ru or my_app.rb files, we need to restart the WEBrick server. To avoid this, let’s use one of Rack’s built-in middleware – Rack::Reloader. Edit config.ru to have:

require './my_app'
use Rack::Reloader
run MyApp.new

In the same folder, we have the my_app.rb file. It’s contents are:

# my_app.rb
class MyApp
  def call env
    [200, {"Content-Type" => "text/html"}, ["Hello Rack Participants from across the globe"]] 
  end
end

To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru

Open a browser window and type the url: http://localhost:9292/.

In your browser window, you should see something like this:

Hello Rack Participants from across the globe

Modify my_app.rb and instead of “Hello Rack Participants from across the globe” type “Hello Rack Participants from across the world!

Refresh the browser window and you should see:

Hello Rack Participants from across the world!

There was no need to re-start the WEBrick server.

You can now press Ctrl-C to stop the WEBrick server.

Writing our own Middleware

For all practical purposes, a middleware is a rack application that wraps up an inner application.

Our middleware is going to do something very simple. We will append some text to the body being sent by our inner application. Let us write our own middleware. We will create a middleware class called MyRackMiddleware. Here’s the skeleton program:

class MyRackMiddleware
  def initialize(appl)
    @appl = appl
  end
  def call(env)
  end
end

In the code above, to get the original body, we initialize the class MyRackMiddleware by passing in the inner application (appl).

Next we need to call this appl:

def call(env)
  status, headers, body = @appl.call(env) # we now call the inner application
end

In the above code, we now have access to the original body, which we can now modify. Rack does not guarantee you that the body would be a string. It could be an object too. However, we are guaranteed that if we call .each on the body, everything it returns will be a string. Let’s use this in our call method:

def call(env)
  status, headers, body = @appl.call(env)
  append_s = "... greetings from RubyLearning!!"
  new_body = ""
  body.each { |string| new_body << " " << string }
  new_body << " " << append_s
  [status, headers, [new_body]]
end

Here’s our completed middleware class:

class MyRackMiddleware
  def initialize(appl)
    @appl = appl
  end
  def call(env)
    status, headers, body = @appl.call(env)
    append_s = "... greetings from RubyLearning!!"
    new_body = ""
    body.each { |string| new_body << " " << string }
    new_body << " " << append_s
    [status, headers, [new_body]]
  end  
end

Our my_app.rb file remains the same. It’s contents are:

# my_app.rb
class MyApp
  def call(env)
    [200, {"Content-Type" => "text/html"}, ["Hello Rack Participants from across the globe"]] 
  end
end

Edit config.ru to have:

require './my_app'
require './myrackmiddleware'
use Rack::Reloader
use MyRackMiddleware
run MyApp.new

To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru

Open a browser window and type the url: http://localhost:9292/.

In your browser window, you should see something like this:

Hello Rack Participants from across the globe... greetings from RubyLearning!!

Another Middleware example

Credit: Matt Aimonetti

Write the following program my_middleware.rb:

# my_middleware.rb
module MyMiddleware
  class Hello
    def initialize(app)
      @app = app
    end
    def call(env)
      if env['PATH_INFO'] == '/hello'
        [200, {"Content-Type" => "text/plain"}, ["Hello from the middleware!"]]
      else
        # forward the request
        @app.call(env)
      end
    end
  end
end

In the above program, we are using a module. Also, we keep the application reference in the @app variable when a new instance of the middleware is created.

my_middleware.rb is a rack middleware living on its own, that you need to require it in your app and then call it by telling rack to use it.

Create our app config.ru as follows:

require './my_middleware'
use MyMiddleware::Hello # this comes in between
run Proc.new{|env| [200, {"Content-Type" => "text/plain"}, ['Try accessing visiting /hello']] }

To run our rack app, in the same folder that contains config.ru, open a command window and type:

$ rackup config.ru

Note: Under the hood, rackup converts your config.ru script to an instance of Rack::Builder (which we shall talk about soon). Rack::Builder#use adds a middleware to the rack application stack created by Rack::Builder.

Open a browser window and type the url: http://localhost:9292/

In your browser window, you should see something like this:

Try accessing visiting /hello

That’s the normal response from your app.

Next, in your browser window type the url: http://localhost:9292/hello

In your browser window, you should see something like this:

Hello from the middleware!

That’s your middleware talking!

Press Ctrl-C in the command window to stop the WEBrick server. Close the command window.

Using Lobster

Rack comes with little funny web application called “Lobster”, which can be used as a demo.

Let us create a config.ru file that contains the following:

require 'rack/lobster'
run Rack::Lobster.new

To run our rack app, in the same folder that contains config.ru, open a command window and type:

$ rackup config.ru

Open a browser window and type the url: http://localhost:9292/. You will see a lobster drawing!

Press Ctrl-C in the command window to stop the WEBrick server. Close the command window.

Previously we wrote a program my_middleware.rb. We can use this with lobster.

Let us create a config.ru file that contains the following:

require 'rack/lobster'
require './my_middleware'
use MyMiddleware::Hello
run Rack::Lobster.new

To run our rack app, in the same folder that contains config.ru, open a command window and type:

$ rackup config.ru

After you have finished executing this program, press Ctrl-C in the command window to stop the WEBrick server. Close the command window.

Rack::Builder

Under the hood, rackup converts your config.ru script to an instance of Rack::Builder.

Rack::Builder is the thing that glues various Rack middlewares and applications together and converts them into a single entity/rack application. A good analogy is comparing Rack::Builder object with a stack, where at the very bottom is your actual rack application and all middlewares on top of it, and the whole stack itself is a rack application too.

Let us create a config.ru file that contains the following:

rack_time = Proc.new { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
Rack::Handler::WEBrick.run rack_time, :Port => 9292

To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru

Open a browser window and type the url: http://localhost:9292/

In your browser window, you should see something like this:

Hello. The time is 2011-12-06 11:19:49 +0530

Press Ctrl-C in the command window to stop the WEBrick server.

In config.ru, let us convert rack_time to use Rack::Builder. We will use the block form of Rack::Builder:

rack_time = Proc.new { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
builder = Rack::Builder.new do
  run rack_time
end
Rack::Handler::WEBrick.run builder, :Port => 9292

Rack::Builder#run specifies the actual rack application you’re wrapping with Rack::Builder.

Here Rack::Builder#initialize accepts a block argument, which is evaluated within the context of the newly created instance using instance_eval. To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru

Open a browser window and type the url: http://localhost:9292/. In your browser window, you should see something like this:

Hello. The time is 2011-12-06 11:19:49 +0530

Press Ctrl-C in the command window to stop the WEBrick server.

Rack::Builder#use adds a middleware to the rack application stack created by Rack::Builder. Rack has many useful middlewares and one of them is Rack::CommonLogger, which logs a single line to the supplied log file in the Apache
common log format.

Let’s add Rack::CommonLogger to rack_time:

require 'logger'
rack_time = Proc.new { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
builder = Rack::Builder.new do
  use Rack::CommonLogger
  Logger.new('rack.log')
  run rack_time
end
Rack::Handler::WEBrick.run builder, :Port => 9292

We create an explicit logger rack.log. This log file is created in the same folder as config.ru and contains:

# Logfile created on 2011-12-06 13:54:42 +0530 by logger.rb/31641

To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru

Open a browser window and type the url: http://localhost:9292/. In your browser window, you should see something like this:

Hello. The time is 2011-12-06 11:19:49 +0530

Press Ctrl-C in the command window to stop the WEBrick server.

Let’s see how to use Rack::Builder#map to map urls to given actions. Let us create a config.ru file that contains the following:

require 'logger'
rack_app = Rack::Builder.new do
  use Rack::CommonLogger
  Logger.new('rack.log')
  map "/" do
    run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["This is public page"]] }
  end
  map "/secret" do
    use Rack::Auth::Basic, "Restricted Area" do |user, password|
      user == 'super' && password == 'secretsauce'
    end
    map "/" do
      run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["This is a secret page"]] }
    end
    map "/files" do
      run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Here are the secret files"]] }
    end
  end
end
Rack::Handler::WEBrick.run rack_app, :Port => 9292

When you nest map blocks, you’ll need to specify URI relative to the enclosing mapping block, as you can clearly see in the example above. We use HTTP Basic Authentication for securing things.

To run our rack app, in the same folder that contains config.ru, type:

$ rackup config.ru

Open a browser window and type the url: http://localhost:9292/.

In your browser window, you should see something like this:

This is public page

If you type the url: http://localhost:9292/secret in your browser window, you should see something like this:

HTTP Basic Authentication
HTTP Basic Authentication

For User Name type super and for Password type secretsauce. Press the OK button. In your browser window, you should see something like this:

This is a secret page

Now, if you type the url: http://localhost:9292/secret/files, in your browser window,
you should see something like this:

Here are the secret files

Do run all the examples in this Rack tutorial, to get a solid understanding of Rack.

Sinatra and Rack Middleware

Sinatra rides on Rack, a minimal standard interface for Ruby web frameworks. As seen earlier, one of Rack’s most interesting capabilities for application developers is support for “middleware” – components that sit between the server and your application monitoring and/or manipulating the HTTP request/response to provide various types of common functionality.

Sinatra makes building Rack middleware easier by using the top-level use method.

Let us see how to use Rack with a Sinatra app. Create a folder racksinatra. Next, let us install Sinatra:

gem install sinatra

Here’s a trivial Sinatra program my_sinatra.rb in the folder racksinatra:

require 'sinatra'
get '/' do
  'Welcome to all'
end

Sinatra can use Rack middleware. Our trivial middleware class will intercept the request from the user, calculate and display the response time on the console and pass on the unaltered request to the Sinatra app. In the folder racksinatra write the middleware class namely rackmiddleware.rb.

class RackMiddleware
  def initialize(appl)
    @appl = appl
  end
  def call(env)
    start = Time.now
    status, headers, body = @appl.call(env) # call our Sinatra app
    stop = Time.now
    puts "Response Time: #{stop-start}" # display on console
    [status, headers, body]
  end
end

In order for Heroku to know what to do with our Sinatra app, create a config.ru in the folder racksinatra:

require "./my_sinatra"
run Sinatra::Application

Modify the Sinatra app to use our middleware:

# my_sinatra.rb
require 'sinatra'
require './rackmiddleware'
use RackMiddleware
get '/' do
  'Welcome to all'
end

Now open a Git Bash shell for folder racksinatra and type:

$ bundle init

Edit the created Gemfile with your preferred text editor to let it look like this:

source "http://rubygems.org"
gem 'sinatra'

In your open Bash shell type:

$ bundle check

Finally in the open Bash shell, type:

$ bundle install

Set up your local app to use Git. Type:

$ git init
$ git add .
$ git commit -m "SinatraRack app"

Create the app on Heroku. In the open bash shell, type:

$ heroku create

On my machine it showed:

Creating warm-winter-6785... done, stack is bamboo-mri-1.9.2
http://warm-winter-6785.heroku.com/ | git@heroku.com:warm-winter-6785.git
Git remote heroku added

Let us rename the app to racksinatra:

$ heroku rename racksinatra

Finally, let us push our app to Heroku:

$ git push heroku master

The app is now running on Heroku. You can take a look at it, in your browser type: http://racksinatra.heroku.com/.

To check whether the *** Response Time ***: has been displayed on the Heroku console, type:

$ heroku logs -s app -n 5

where -s app shows the output from your app and -n 5 retrieves 5 log lines.

For me it showed:

?[36m2011-10-26T05:40:06+00:00 app[web.1]:?[0m *** Response Time ***: 0.000297385

Next, let’s use our previously written middleware program namely my_middleware.rb. In the same folder write a program and call it my_sinatra.rb, as follows:

require 'sinatra'
require './my_middleware'
use MyMiddleware::Hello
get '/' do
  "Hello Ruby participants from across the globe!"
end

Open a command window and type:

$ ruby my_sinatra.rb

Open a web browser and navigate to http://localhost:4567/hello. Your Sinatra application should respond with our greeting namely:

Hello from the middleware!

In the open command window, press Ctrl C to stop Sinatra. Close the command window.

Sinatra and Rack HTTP Basic Authentication

You can easily protect your Sinatra application using HTTP Basic Authentication with the help of Rack middlewares. In the same folder type the program my_sinatra2.rb:

require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |user, password|
  user == 'super' && password == 'secretsauce'
end
get '/' do
  "This is a secret page"
end
get '/files' do
  "Here are the secret files"
end

Open a command window and type:

$ ruby my_sinatra2.rb

Open a web browser and navigate to http://localhost:4567. Your Sinatra application should respond by asking you for the User Name and Password. Enter “super” and “secretsauce” respectively and the browser page would display:

This is a secret page

If you type the url: http://localhost:4567/files in your browser window you should see
something like this:

Here are the secret files

In the open command window, press Ctrl C to stop Sinatra. Close the command window.

That’s it. I hope you liked this introductory article on Rack. Happy Racking!!

References

is an author and founder of RubyLearning.com and RubyLearning.org where over 35000 participants have learnt Ruby programming from across the globe.

Posted by Satish Talim

{ 18 comments… read them below or add one }

Rahul Trikha October 26, 2011 at 12:41 pm

Thanks this is a very well written Rack article Satish.

Reply

Gonzih October 26, 2011 at 1:24 pm

Very nice article! Thanks!

Reply

Olivier de Robert October 26, 2011 at 2:59 pm

Thanks for the good work !

Reply

Luc Heinrich October 26, 2011 at 3:13 pm

Just a small note: if require “rack” returns false it does not mean that Rack is not installed, it means that it is actually installed and has already been required. If Rack is not installed you’ll get a LoadError exception.

Reply

Satish Talim October 26, 2011 at 3:17 pm

Luc, thanks for the correction. Edited the article as per your suggestion.

Reply

Lance Woodson October 27, 2011 at 5:51 pm

Good article, thanks. One thing that might be of additional use to beginners is to list some common problems that middleware can solve more elegantly than other solutions.

Reply

Marcos Maia October 31, 2011 at 1:07 pm

Nice article! Thanks. This help-me a lot

Reply

Sumit Bisht November 2, 2011 at 7:00 pm

Very well written article.
Thank you for sharing this !

Reply

apneadiving November 2, 2011 at 7:03 pm

Very interesting article, thanks a bunch.

Reply

Gavin Morrice November 3, 2011 at 2:18 pm

Hey Satish, great walkthrough – thanks!

I think the git installation was unnecessary though – a little off topic

Reply

Satish Talim November 3, 2011 at 2:32 pm

Thanks Gavin. I agree that some of the topics were off topic, but I wanted to hand-hold a complete Ruby novice.

Reply

Jay Godse November 5, 2011 at 7:52 am

Thanks for that Satish. It is a very nice introduction to Rack which anybody with irb can replicate. It is very useful.

Reply

Hrvoje Simic May 25, 2012 at 5:01 pm

Thank you for an excellent tutorial, Satish!

I had to adjust your code a bit. When I use Rack::Handler::WEBrick, my SIGINT doesn’t get caught. I have to terminate the process with SIGTERM instead. I’ve looked it up in the source and found that the Rack::Handler::WEBrick#run is indeed trapping the SIGINT, but why doesn’t it work like it should remains a mystery to me.

Using Thin instead works like a charm.

Reply

Christian Guimarães July 6, 2012 at 4:28 pm

Nice article Satish!

Good and clear explanation. Straight to the point.

Just a remark in the phrase “This can be done by calling the lambda method of the class Object”. For a newcomer this expression can lead to a wrong understanding of the Ruby object model. ‘lambda’ is a method from the Kernel module that is included in the Object class. I think is better explain this like: “This can be done by calling the lambda method included in the Object class”.

Cheers!

@csgui

Reply

Luc Juggery September 21, 2012 at 2:29 pm

Hello Satish,

Thanks for this great tutorial, it clarifies a lot of things.

Regards,
Luc

Reply

7stud December 13, 2012 at 12:39 pm

You say:
====
Rack does not guarantee you that the body would be a string. It could be an object too. However, we are guaranteed that if we call .each on the body, everything it returns will be a string. Let’s use this in our call method:

append_s = “… greetings from RubyLearning!!”
[status, headers, body << append_s]
====

That passage implies that if the body responds to each(), then it also responds to <<(). However, that is not true. Here is an example:

====

class MyClass
def initialize
@arr = ["hello", "world"]
end

def each(&block)
@arr.each(&block)
end
end

obj = MyClass.new

obj.each do |item|
p item
end

obj << "goodbye"

–output:–
"hello"
"world"
1.rb:16:in `’: undefined method `<<' for # (NoMethodError)
====

Yet, according to your article, a MyClass object fits the Rack requirements for the body because:

1) It responds to each().
2) each() returns Strings.

So your MyRackMiddleware is buggy because it will throw an error for some valid body’s. You can fix MyRackMiddleware with this code:

====
class MyRackMiddleware
def initialize(appl)
@appl = appl
end

def call(env)
status, headers, body = @appl.call(env)
append_s = “… greetings from RubyLearning!!”

new_body = “”
body.each { |string| new_body << " " << string }
new_body << " " << append_s

[status, headers, [new_body]]
end
end
====

That version of MyRackMiddleware will never fail when the body meets the rack requirements.

Reply

Erik Isaksen December 20, 2012 at 4:20 am

Great introduction Satish! Thanks!

Reply

JuanitoFatas July 3, 2013 at 1:11 pm

Great! Great! Great introduction!

Reply

Leave a Comment