Posts /

Rack

Twitter Facebook Google+
18 May 2016

Today we will explore Rack. Rack is a Ruby library that sits between your web app and your HTTP server of choice. Essentially it provides a range of conveniences for interacting with and responding to web requests.

First things first, get yourself the libraries we will be playing with:

gem install pry # the best REPL I can think of
gem install rack

Fire up pry, and take a quick peek at Rack:

require 'rack'
 
 # Since we're using Pry, we can enjoy its super inspection powers!
 cd Rack
 ls Rack
 constants:
   Auth           Chunked         CONTENT_TYPE   ETag            Head    MethodOverride  NullLogger    Request         Sendfile        Static
   BodyProxy      CommonLogger    ContentLength  File            HEAD    Mime            PATH_INFO     REQUEST_METHOD  Server          TempfileReaper
   Builder        ConditionalGet  ContentType    ForwardRequest  Lint    MockRequest     QUERY_STRING  Response        Session         URLMap
   CACHE_CONTROL  Config          Deflater       GET             Lock    MockResponse    Recursive     Runtime         ShowExceptions  Utils
   Cascade        CONTENT_LENGTH  Directory      Handler         Logger  Multipart       Reloader      SCRIPT_NAME     ShowStatus      VERSION
 Rack.methods: release  version
 locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_
 

Remember, Rack sits between the server and your app. Let’s ‘pry’ open the server and take a look:

 # perform some more Pry magic on Rack::Server.start to see how Rack gets going.
 cd Rack
 ls Server
 show-doc Server.start
 ...
 This method can be used to very easily launch a CGI application, for
 example:
 
  Rack::Server.start(
    :app => lambda do |e|
      [200, {'Content-Type' => 'text/html'}, ['hello world']]
    end,
    :server => 'cgi'
  )

The docs here give us a valid way to run Rack, nice! Though I hope I am not the only one who finds lamdbas awkward. A dip into the official documentation, or any Rack tutorial, will tell you that a typical Rack app is essentially this:

require 'rack'

class App
  def call(env)
    [200, {}, ['Hello World!']] 
  end
end

Rack::Server.start(app: App.new)

Which, when visiting ‘localhost’, at the port written in your console, should display ‘Hello World!’ in your browser.

The Rack specification says that a Rack compliant app is an object that has the method ‘call’, which accepts one argument, and returns an Array with three entries: the http status, a hash of http headers and an enumerable with the content you want displayed in the browser.

Let’s keep digging. Break out the above code into a file and drop a ‘pry.binding’ inside the call method.

# /tmp/racksploration.rb
*snip*

def call(env)
  binding.pry
  [200, {}, ['Hello World!']] 
end

*snip*

Running this and visiting the web page will allow us to inspect Rack in action!

$ ruby /tmp/rackit.rb
 
Puma 2.11.0 starting...
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:8080

From: /private/tmp/rackit.rb @ line 6 App#call:

    5: def call(env)
    6: 	binding.pry # <= you're here 
    7: 	[200, {}, ['Hoi dur!']]
    8: end

[1] pry(#<App>)> env
=> {"rack.version"=>[1, 3],
...
  "rack.after_reply"=>[]}

What we discover is an array of values that will be familiar to anyone who has played with web frameworks or servers, plus a few additions from Rack itself.

Here on out we will see how Rack really begins to shine as an excellent foundation for web frameworks like Sinatra and Rails.

# Convenient interfaces for interacting with and building request/responses
# rack_params.rb
require 'rack'

class App
  def call(env)
    req = Rack::Request.new(env)          # start request
    name = req.params['name'] || 'there'  # grab params passed by url
    data = "Hullo #{name}!"               # make return data
    res = Rack::Response.new(data)        # create response
    res.finish                            # response helper builds the array
  end
end

Rack::Server.start(app: App.new)

Our first interactive Rack application! Go to the browser and try adding a name query parameter to the url, like: /?name=LumpySpacePrincess and watch the name in your browser change.

There is one final feature of Rack I would like to cover - Middleware. The secret sauce of Rack. What if I told you we can write discrete, little Rack apps and stack them up as veritable programming lego.

Naturally, rack provides a convenience for this:

require 'rack'
require 'pry'

class Middleman  # middleware class with additional functionality.
  def initialize(app)
    @app = app
  end

  def call(env) # Notice the structure is very similar to the App class.
     env = Rack::Request.new env
     if env.params['name']
       env.params['name'] = 'Middleman ' + env.params['name'] + ' Middleman'
     end
     @app.call env
  end
end

class App
  def self.call(env)
    name = env.params['name'] || 'there'
    data = "Hullo #{name}!"
    [200, {}, [data]]
  end
end

app = Rack::Builder.new do
  use Middleman
  run App
end

Rack::Server.start(app: app)

If you drop ‘binding.pry’ into ‘Middleman’ you will discover that the middleware is called twice, here’s why:

HTTP_Request => Rack => Middleman => App => Middleman => Rack => Http_Response

Middleware is a stack that is first traversed down, then up. The middleware thus has the chance to filter the request twice.

The best part of all. Middleware are extremely reusable. Write middleware for one app, you can probably use it for your next one - and the Rack devs, and the community, have written many many more for us to drop in as we please.

# A flavour of available middleware:
# https://github.com/rack/rack/wiki/List-of-Middleware
# https://github.com/rack/rack-contrib 

app = Rack::Builder.new do
  use Rack::Cache
  use Rack::GoogleAnalytics # no more dirty templates!
  use Rack:Attack           # for blocking & throttling abusive requests 
  use Middleman
  run App
end

It is my hope with this little Rackventure that you will come away a bit more enlightened about the magic of Rack as the underpinning of popular Ruby web frameworks. You should also have a taste of how easy it is to write your own middleware and with a little reading you will be able to see how your own middleware can be plugged into your framework of choice easily. I also hope you will begin to see how one could begin planning to build a web framework on top of Rack by embracing middleware.

I feel there is a lot of power here and few (1,2) discoveries are lending weight to this notion. As I consume Sinatra and Rails more I will be eagerly looking for opportunities to leverage middleware in my architecture.

Until next time.


Twitter Facebook Google+