Ben Brinckerhoff



I'm Ben Brinckerhoff.
email
Twitter
del.icio.us
GitHub
LinkedIn

Kiva - loans that change lives


Text

Apr 23, 2010
@ 11:20 am
Permalink

Intermediate Rack Middleware Tips

UPDATE: Upon further research (and help from others in the Ruby community), I realized that cloning the Rack env is a bad idea. I’ve added some explanation as to why below).

I recently wrote my first Rack middleware (source code, gem). I won’t bother giving an introduction to Rack or Rack middleware, because there are plenty of fine introductory tutorials already.

However, I found that once I got beyond the basic Rack middleware examples, I didn’t find many tips on how to build more ‘real-world’ examples. Here is a collection of tips for intermediate use - I’m definitely no expert.

1. Read the Spec

I’m admittedly pretty bad about reading specs, but trust me, the Rack spec is super short and very readable. I found out all kinds of useful information. For example, many “Hello, world” Rack tutorials show a response body that is a simple string, but according to the spec, “the Body should not be an instance of String”. Good to know.

2. Use Rack::Line in Your Tests/Specs

Rack::Lint is included in the Rack gem and “validates your application and the requests and responses according to the Rack spec.”

I found it helpful to add a test that used Rack::Lint. Here is some silly middleware that just capitalizes a body and adds a “!” to the end.

If you run this as-is, you’ll find that Rack::Lint finds a bug: it fails the test with the error “Rack::Lint::LintError: Content-Length header was 0, but should be 6” because I set the Content-Length header incorrectly.

(Interestingly, as of Rack 1.1.0, Rack::Lint doesn’t complain about a response body that is a String and it doesn’t catch Content-Length errors if your response body is a String! So you’ll actually get better error checking if you’ve read the spec and return a non-String body)

3. Clone the Rack env.

Each call to the ‘call’ method passes in a environment hash. To avoid confusing bugs, I would recommend cloning this hash. If you modify the hash, you have no guarantee that ‘parent’ apps have cloned the hash, so you might mess up their behavior in weird ways. Even if you only read values from the hash, you have no guarantee that ‘child’ apps won’t modify the env hash, so be safe and clone it before you pass it along.

UPDATE: Don’t clone the rack environment. If you simply call ‘env.clone’, you’ll only be doing a shallow clone, which won’t really help avoid bugs.

If you try to do a deep clone, you’ll find that some types (like IO and Procs) can’t be cloned anyway.

And, most importantly, you’ll break code. Rack::Session::Cookie, Rack::Flash, and Devise all hold onto copies of the Rack env. If your code clones the env and the underlying app changes it, those libraries won’t be able to notice the changes.

Here’s a simple example of how cloning the env will break sessions: 

More Resources

If you want more examples of Rack middleware techniques, I highly recommend downloading some source and reading through it. Most middleware is really quite easy to understand once you get the basic pattern. Personally, I found that looking through the collection of middleware in rack-contrib was really helpful.

Other ideas?

As I said, I’m certainly no Rack middleware expert. If you had additional tips or links to good resources, please add them in the comments below!