Wrapping Rack Middleware to Exclude Certain URLs (For Rails Streaming Responses)

Another one of the tweaks I had to make to accomodate Rails streaming responses was ensuring that the Rack::Deflater middleware didn't run on my app's streaming actions.

The Deflater middleware gzips your responses before delivering them to the client. This is useful to run on the Heroku Cedar stack because this facility is otherwise not provided. There's a big gotcha, though: the middleware breaks Rails HTTP streaming responses. There's a good explanation of this on StackOverflow, as well as a monkey patch to ActionController::Streaming that corrects the behaviour.

I took an alternative, simpler approach. I extended the middleware with support for excluding a URL pattern. This way, I can just disable it on the few actions where I know I am streaming responses. Here's how it looks:

module Rack
  class DeflaterWithExclusions < Deflater
    def initialize(app, options = {})
      @app = app

      @exclude = options[:exclude]
    end

    def call(env)
      if @exclude && @exclude.call(env)
        @app.call(env)
      else
        super(env)
      end
    end
  end
end

And here's how the extended middleware is included, in config.ru:

use Rack::DeflaterWithExclusions, :exclude => proc { |env|
  env['PATH_INFO'] == '/order/purchase'
}

Using an executable proc for the :exclude argument is useful because it then allows for far more sophisticated URL matching than simple string equality (in production, I actually check against a couple of regular expressions).

Wrapping the middleware in this way has worked well. We get to keep it for the bulk of the app, where it's useful, without it interfering with the parts that are more touchy. It would be nice to see this kind of :exclude option handling become more common among Rack middlewares.