Friday, June 5, 2009

Prototype to Learn: Rack

‹prev | My Chain | next›

After a brief interlude into silly Ruby arcana, I am back to Rack tonight. There are some gaps in my understanding of Rack, so I will prototype to learn.

The other night, I was able to get my Sinatra app running under Rack by creating a rackup file with the following contents:
require 'eee'
run Sinatra::Application
So let's see if we can add a handler in front of the Sinatra app. For example I will try to force the user to look at an interstitial every fifth access of the site.

As with any Rack middleware, I need to initialize with a single argument (the Rack application) and expose a call method that accepts the web environment:
module Rack
class Interstitial
def initialize app
@app = app

def call env
session = env["rack.session"]
session[:count] = session[:count].to_i + 1
if session[:count] % 5 == 0
return [200, { }, session[:count].to_s]
end env
To get access to that session, I need to use Rack::Session::Cookie (and my newly created Rack::Interstitial:
use Rack::Session::Cookie

use Rack::Interstitial
With each access, Rack will first hand off to Rack::Session::Cookie, which adds the cookie values to the environment under the value "rack.session" by default. Rack::Session::Cookie will then call Rack::Interstitial.

Each access to Rack::Interstitial pulls a running count from the session and increments the count by 1. Normally it will then call the next Rack module, but every time the session count is divisible by 5 with no remainder, the Rack::Interstitial middleware will return HTTP response code 200 (OK), with no special headers ({ }) and content consisting entirely of the current session count.

To verify that I have implemented this correctly, I load up one of our meals in the browser:

So far, so good. What happens if I reload 4 more times?

Nice! The interstitial kicks in as expected.

Before I call it a day, I am curious about manipulating output. Since I am already a third of the way to fizz buzz glory, I think I'll try to modify the output such that every instance of the word "grits" is replaced by "fizz" every third access.

If I was doing this legit, I would make this another Rack handler. Since I am just learning, I'll do this right in my interstitial handler. Every Rack handler's call method has to respond with three things: the HTTP response code, the HTTP headers and the response body. So I ought to be able to take that response body and do a global substitiute:
   code, headers, body = env
[code, headers, body.gsub(/grits/i, 'fizz')]
That does not quite work:

I get an error calling gsub on a Rack::CommonLogger instance. Ah, that is not the response body, just an object that responds to each with the response body. Guess I'll have to do it the hard way:
      code, headers, body = env
if session[:count] % 3 == 0
ret = []
body.each do |line|
ret << line.gsub(/grits/i, '<span style="color:red">fizz</span>')
ret = body
[code, headers, body]
With that, I get my desired "Sausage Vegetable fizz":


There is still some other areas of Rack that I would like to explore, so I will probably pick back up prototyping tomorrow.

No comments:

Post a Comment