如何仅针对特定路径使用Rack中间件?

时间:2009-05-28 13:02:37

标签: ruby rack middleware

我想在我的Rack应用中运行MyMiddleware,但仅适用于某些路径。我希望使用Rack::Builder或至少Rack::URLMap,但我无法弄清楚如何使用。

这是我认为可行的,但不是:

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

或者,更好的是,使用Regexp:

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

map似乎最终要求一个应用程序;它不会仅仅将控制权传回给它的父母。 (当Rack尝试将undefined method 'each' for nil:NilClass块的结尾变为do...end时,实际错误为“app”。

是否有一个中间件需要一系列中间件和路径,并且只在路径匹配时运行它们?

2 个答案:

答案 0 :(得分:13)

你可以让MyMiddleware检查路径,如果匹配则不将控制传递给下一件中间件。

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

或者,你可以使用没有dslness的URLMap。它看起来像这样:

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

URLMap实际上是pretty simple to grok

答案 1 :(得分:10)

这不起作用,因为@app在正确的范围内不存在:

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

但这会:

# 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将第一个参数从路径的前面剥离到map,因此它不会无休止地递归。不幸的是,这意味着在该路径前缀被剥离后,路径的其余部分不太可能与其他映射正确匹配。

以下是一个例子:

::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

转到/html/xml会导致以下内容转到日志:

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

也就是说,应用映射到'/html'前缀的'/html'条,现在调用与映射到'/xml'的应用匹配。