How do I use a Rack middleware only for certain paths?

James A. Rosen picture James A. Rosen · May 28, 2009 · Viewed 10.1k times · Source

I'd like to have MyMiddleware run in my Rack app, but only for certain paths. I was hoping to use Rack::Builder or at least Rack::URLMap, but I can't quite figure out how.

This is what I thought would work, but doesn't:

# in my rackup file or Rails environment.rb:
map '/foo' do
  use MyMiddleware, { :some => 'options' }
end

Or, better yet, with a Regexp:

map /^foo/ do
  use MyMiddleware, { :some => 'options' }
end

But map seems to demand an app at the end; it won't fall back on just passing control back to its parent. (The actual error is "undefined method 'each' for nil:NilClass" from when Rack tries to turn the end of that do...end block into an app.)

Is there a middleware out there that takes an array of middlewares and a path and only runs them if the path matches?

Answer

BaroqueBobcat picture BaroqueBobcat · Jul 28, 2009

You could have MyMiddleware check the path and not pass control to the next piece of middle ware if it matches.

class MyMiddleware
  def initialize app
    @app = app
  end
  def call env
    middlewary_stuff if env['PATH_INFO'] == '/foo'
    @app.call env
  end

  def middlewary_stuff
    #...
  end
end

Or, you could use URLMap w/o the dslness. It would look something like this:

main_app = MainApp.new
Rack::URLMap.new '/'=>main_app, /^(foo|bar)/ => MyMiddleWare.new(main_app)

URLMap is actually pretty simple to grok.