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

我想在我的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似乎最终需要一个应用程序; 它不会仅仅将控制权传回给它的父母。 (实际错误是“ undefined method 'each' for nil:NilClass ”从Rack试图将那个结束时do...end块变成app 。)

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

您可以让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 非常简单 。

这不起作用,因为@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, {}, "

Hello!

" ] } 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'的应用程序。