Advertisement
Ruby

Exploring Rack

by

If you're a Ruby programmer who has done any kind of web development, you've almost certainly used Rack, whether you know it or not, as it's the foundation which most Ruby web frameworks (Rails, Sinatra, etc.) are built upon. Let's dig into some of the basic concepts of Rack and even build a small app or two.


What Is Rack, Exactly?

Rack is several things, actually:

  • a web server interface
  • a protocol for building and composing web applications
  • a collection of middleware utilities

A Web Server Interface

Part of what's nice about Rack, is that it provides a standardized way for Ruby applications to talk to web servers, abstracting away the server stuff (listening on a port, accepting connections, parsing HTTP requests and responses, etc.) so that you can focus on what your application does.


The Protocol

The Rack protocol is very simple: a Rack application is simply a Ruby object with a call method. That method should accept an environment hash describing the incoming request and return a three-element array in the form of: [status, headers, body], where:

  • status is the HTTP status code.
  • headers is a hash of HTTP headers for the response.
  • body is the actual body of the response (e.g. the HTML you want to
    display). The body must also respond to each.

The easiest way to understand Rack's protocol, is by taking a look at some code.

First, get the rack gem and set up a directory:

$ gem install rack
$ mkdir hellorack
$ cd hellorack

Now create a file named config.ru and fill it in with the following:

class Hello
  def self.call(env)
    [ 200,                              # 200 indicates success
      {"Content-Type" => "text/plain"}, # the hash of headers
      ["Hello from Rack!"]              # we wrap the body in an Array so
                                        # that it responds to `each`
    ]
  end
end

# Tell Rack what to run our app
run Hello

Save the file, open up your terminal and run the rackup command:

$ rackup
[2012-12-21 17:48:38] INFO  WEBrick 1.3.1
[2012-12-21 17:48:38] INFO  ruby 1.9.2 (2011-07-09) [x86_64-darwin11.0.1]
[2012-12-21 17:48:38] INFO  WEBrick::HTTPServer#start: pid=1597 port=9292

The bottom few lines of output, in the above code, let you know that Rack is running your app at port 9292 using Ruby's built-in WEBrick web server. Point your browser to http://localhost:9292 to see a happy welcome message from Rack.

Kill the app (ctrl-c) and let's talk about what is going on here.

When you run the rackup command, Rack looks for a rackup config file (conventionally named config.ru, but you can name it whatever you want). It then starts a web server (WEBrick by default) and runs your app.

This protocol is the foundation on which popular frameworks like Rails and Sinatra are built. What they do is layer functionality like template rendering, route dispatching, managing database connections, content negotiation, etc. on top of this fundamental abstraction.

How they do this, is what brings us to the concept of middleware.


Middleware

Middleware gives you a way to compose Rack applications together.

A middleware (yes, it's both singular and plural) is simply a Rack application that gets initialized with another Rack application. You can define different middleware to do different jobs and then stack them together to do useful things.

For example, if you have a Rails app lying around (chances are, if you're a Ruby developer, that you do), you can cd into the app and run the command rake middleware to see what middleware Rails is using:

$ cd my-rails-app
$ rake middleware
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fcc4481ae08>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
run MyRailsApp::Application.routes

Every request that comes into this app starts at the top of this stack, bubbles its way down, hits the router at the bottom, which dispatches to a controller that generates some kind of response (usually some HTML), which then bubbles its way back up through the stack before being sent back to the browser.


A Middleware Example

Nothing fosters understanding a new concept like coding does, so let's build a very simple middleware that just converts the response body to uppercase. Open up our config.ru file from before and add the following:

class ToUpper
  # Our class will be initialize with another Rack app
  def initialize(app)
    @app = app
  end

  def call(env)
    # First, call `@app`
    status, headers, body  = @app.call(env)

    # Iterate through the body, upcasing each chunk
    upcased_body = body.map { |chunk| chunk.upcase }

    # Pass our new body on through
    [status, headers, upcased_body]
  end
end

# This is the same Hello app from before, just without all the comments
class Hello
  def self.call(env)
    [ 200, {"Content-Type" => "text/plain"}, ["Hello from Rack!"] ]
  end
end

use ToUpper # Tell Rack to use our newly-minted middleware
run Hello

Run the rackup command again and visit http://localhost:9292 to see our new middleware in action.

What Rack did here was build a Rack application that was the composition of the ToUpper and Hello applications. Internal to Rack, there's a Builder class that effectively constructed a new app by doing the equivalent of:

app = ToUpper.new(Hello)
run app

If there were more middleware present (like in the Rails stack), it would just nest them all the way down:

use Middleware1
use Middleware2
use Middleware3
run MyApp

#=> Boils down to Middleware1.new(Middleware2.new(Middleware3.new(MyApp)))

Request and Response Classes

When you start writing Rack applications and middleware, manipulating the [status, headers, body] array quickly becomes tedious.

Rack provides a couple of convenience classes, Rack::Request and Rack::Response, to make life a little bit easier.

Rack::Request wraps an env hash and provides you with convenience methods for pulling out the information you might need:

def call(env)
  req = Rack::Request.new(env)
  req.request_method #=> GET, POST, PUT, etc.
  req.get?           # is this a GET requestion
  req.path_info      # the path this request came in on
  req.session        # access to the session object, if using the
  # Rack::Session middleware
  req.params         # a hash of merged GET and POST params, useful for
  # pulling values out of a query string

  # ... and many more
end

Rack::Response is complementary to Rack::Request, and gives you a more convenient way to construct a response. For example, our Hello app could be rewritten as follows:

class Hello
  def self.call(env)
    res = Rack::Response.new

    # This will automatically set the Content-Length header for you
    res.write "Hello from Rack!"

    # returns the standard [status, headers, body] array
    res.finish

    # You can get/set headers with square bracket syntax:
    #   res["Content-Type"] = "text/plain"

    # You can set and delete cookies
    #   res.set_cookie("user_id", 1)
    #   res.delete_cookie("user_id")
  end
end

Conclusion

In this article, we've covered the basic concepts of Rack, which should be enough for you to get a better understanding of what's under the hood of the many popular frameworks out there and also help you get your feet wet if you're interested in developing directly with Rack.

Code is an excellent teacher, and so if you're interested in Rack, I highly recommend looking at its source. It comes with a lot of very useful baked-in utilities and middleware (and plenty more at rack-contrib) that you can use and learn from.

Related Posts
  • Code
    Ruby
    Writing Robust Web Applications - The Lost Art of Exception HandlingRails education retina preview2
    As developers, we want the applications we build to be resilient when it comes to failure, but how do you achieve this goal? If you believe the hype, micro-services and a clever communication protocol are the answer to all your problems, or maybe automatic DNS failover. While that kind of stuff has its place and makes for an interesting conference presentation, the somewhat less glamorous truth is that making a robust application begins with your code. But, even well designed and well tested applications are often lacking a vital component of resilient code - exception handling.Read More…
  • Code
    Ruby
    5 Reasons Why New Relic Is a Developer's Best FriendGetting started new relic retina preview2
    Once you start digging around New Relic you begin to realise just how many interesting features the service has to help monitor the performance and health of your application. It was truly difficult to pick just five things to talk about, so rather than focusing on the obvious features let's look at some of the less hyped functionality that New Relic provides and how we can use it in interesting and sometimes unorthodox ways. When we left you last time, we had a basic 'Hello World' Rails application (called New Relic_rails1, living in ~/project/tmp/New Relic). We will continue using this app, extend it and see if we can use it to demonstrate the features of New Relic that we'll be looking at.Read More…
  • Code
    Ruby
    Getting Started With New Relic in 30 MinutesGetting started new relic retina preview2
    I remember working on a Rails app a few years ago and someone floated the idea of using this new service that had appeared on the scene. It was called New Relic and they were promising to give you more insight into the performance of your Rails app, than you ever could get before. We gave it a try and it was impressive, more importantly it was something the Ruby web development ecosystem truly needed.Read More…
  • Code
    Creative Coding
    A Look at the WordPress HTTP API: wp_remote_get - the ResponseDiagram http api
    In this series, we've been taking a look at the wp_remote_get WordPress HTTP API function in order to understand how it works, how we can use it, and the optional arguments that it accepts. From here, we're able to write detailed requests; however, that's only half of it - there's also the response. In the second article, we took a look at what a basic response would look like, how to evaluate it, and how to display it on the screen, but we didn't actually talk about the response in detail. If you're looking to write more advanced requests and write more defensive code, then it's important to understand the data that's sent as a response. In this final article, we're going to do exactly that.Read More…
  • Code
    Ruby
    Digging Into Rails 4Digging rails
    Rails 4 is rapidly approaching. In this article, let's take a look at some of the new features that it offers, as well as the changes that may affect your current applications.Read More…
  • Code
    Tools & Tips
    HTTP: The Protocol Every Web Developer Must Know - Part 1Http2 http
    HTTP stands for Hypertext Transfer Protocol. It's a stateless, application-layer protocol for communicating between distributed systems, and is the foundation of the modern web. As a web developer, we all must have a strong understanding of this protocol.Read More…