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

半世苍凉 提交于 2019-11-30 12:23:44

问题


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?


回答1:


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.




回答2:


This doesn't work because @app doesn't exist in the right scope:

# in my_app.ru or any Rack::Builder context:
@app = self
map '/foo' do
  use MyMiddleware
  run lambda { |env| @app.call(env) }
end

But this will:

# in my_app.ru or any Rack::Builder context:
::MAIN_RACK_APP = self
map '/foo' do
  use MyMiddleware
  run lambda { |env| ::MAIN_RACK_APP.call(env) }
end

Rack::Builder strips the first argument to map off the front of the path, so it doesn't endlessly recurse. Unfortunately, this means that after that path prefix is stripped off, it's unlikely that the rest of the path will properly match other mappings.

Here's an example:

::MAIN_APP = self
use Rack::ShowExceptions
use Rack::Lint
use Rack::Reloader, 0
use Rack::ContentLength

map '/html' do
  use MyContentTypeSettingMiddleware, 'text/html'
  run lambda { |env| puts 'HTML!'; ::MAIN_APP.call(env) }
end

map '/xml' do
  use MyContentTypeSettingMiddleware, 'application/xml'
  run lambda { |env| puts 'XML!'; ::MAIN_APP.call(env) }
end

map '/' do
  use ContentType, 'text/plain'
  run lambda { |env| [ 200, {}, "<p>Hello!</p>" ] }
end

Going to /html/xml causes the following to go to the log:

HTML!
XML!
127.0.0.1 - - [28/May/2009 17:41:42] "GET /html/xml HTTP/1.1" 200 13 0.3626

That is, the app mapped to '/html' strips of the '/html' prefix and the call now matches the app mapped to '/xml'.



来源:https://stackoverflow.com/questions/920719/how-do-i-use-a-rack-middleware-only-for-certain-paths

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!