如何在Rails middware中找到当前的抽象路由

Sau*_*abh 9 ruby ruby-on-rails spree ruby-on-rails-4

Rails版本:'〜> 4.2.7.1'

狂欢版:'3.1.1'

TlDr: 如何/api/products/:id在Rails 4应用程序的中间件中获取该路由的路由或控制器和操作.

细节:

我在rails应用程序中添加了一个类似于gem scout_statsd_rack的中间件.这会将以下中间件添加到rails应用程序以通过statsd发送指标:

def call(env)
  (status, headers, body), response_time = call_with_timing(env)
  statsd.timing("#{env['REQUEST_PATH']}.response", response_time)
  statsd.increment("#{env['REQUEST_PATH']}.response_codes.#{status.to_s.gsub(/\d{2}$/,'xx')}")
  # Rack response
  [status, headers, body]
rescue Exception => exception
  statsd.increment("#{env['REQUEST_PATH']}.response_codes.5xx")
  raise
end

def call_with_timing(env)
  start = Time.now
  result = @app.call(env)
  [result, ((Time.now - start) * 1000).round]
end
Run Code Online (Sandbox Code Playgroud)

我想要的是在中间件中找到当前路由,以便我可以发送特定于每个路由的度量.

我尝试了这里描述的方法,它告诉我们env['PATH_INFO']可以提供路径,但是它提供了这样的URL参数:/api/products/4但我想要的是/api/products/:id因为我的目的是跟踪/api/products/:idAPI的性能 .

env['REQUEST_PATH']env['REQUEST_URI']给出相同的回应.

我试过这里这里提供的答案:

Rails.application.routes.router.recognize({"path_info" => env['PATH_INFO']})
Run Code Online (Sandbox Code Playgroud)

或者像这样

Rails.application.routes.router.recognize(env['PATH_INFO'])
Run Code Online (Sandbox Code Playgroud)

但它给出了以下错误:

NoMethodError(未定义方法find_routes'vendor /bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:59:in call'path_info' for {"path_info"=>"/api/v1/products/4"}:Hash):
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:100:in

recognize'
vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in

这个答案讨论了request.original_url,但我如何访问变量request,我认为它应该是相同的env但不能从此获得路由.

编辑#1

你可以看到样品回购这里,护栏中间件的代码在这里,可以做的这个设置项记载README,比这个API被打:http://localhost:3000/api/v1/products/1.

编辑#2

我试过@MichałMłoźniak给出的方法如下:

def call(env)
  (status, headers, body), response_time = call_with_timing(env)
  request = ActionDispatch::Request.new(env)
  request = Rack::Request.new("PATH_INFO" => env['REQUEST_PATH'], "REQUEST_METHOD" => env["REQUEST_METHOD"])
  Rails.application.routes.router.recognize(request) { |route, params|
    puts "I am here"
     puts params.inspect
     puts route.inspect
   }
Run Code Online (Sandbox Code Playgroud)

但我得到了以下回应:

I am here 
{}
#<ActionDispatch::Journey::Route:0x007fa1833ac628 @name="spree", @app=#<ActionDispatch::Routing::Mapper::Constraints:0x007fa1833ace70 @dispatcher=false, @app=Spree::Core::Engine, @constraints=[]>, @path=#<ActionDispatch::Journey::Path::Pattern:0x007fa1833acc90 @spec=#<ActionDispatch::Journey::Nodes::Slash:0x007fa1833ad230 @left="/", @memo=nil>, @requirements={}, @separators="/.?", @anchored=false, @names=[], @optional_names=[], @required_names=[], @re=/\A\//, @offsets=[0]>, @constraints={:required_defaults=>[]}, @defaults={}, @required_defaults=nil, @required_parts=[], @parts=[], @decorated_ast=nil, @precedence=1, @path_formatter=#<ActionDispatch::Journey::Format:0x007fa1833ac588 @parts=["/"], @children=[], @parameters=[]>>
Run Code Online (Sandbox Code Playgroud)

我也在这里推动了变化.

Mic*_*iak 9

你需要传递ActionDispatch::RequestRack::Requestrecognize方法.这是另一个应用程序的示例:

main:0> req = Rack::Request.new("PATH_INFO" => "/customers/10", "REQUEST_METHOD" => "GET")
main:0> Rails.application.routes.router.recognize(req) { |route, params| puts params.inspect }; nil
{:controller=>"customers", :action=>"show", :id=>"10"}
=> nil
Run Code Online (Sandbox Code Playgroud)

同样适用ActionDispatch::Request.在中间件中,您可以轻松创建此对象:

request = ActionDispatch::Request.new(env)
Run Code Online (Sandbox Code Playgroud)

如果您需要有关已识别路径的更多信息,可以通过recognize方法查看要阻塞的路径对象.

更新

上述解决方案适用于普通的Rails路由,但由于您只安装了spree引擎,因此需要使用不同的类

request = ActionDispatch::Request.new(env)
Spree::Core::Engine.routes.router.recognize(request) { |route, params|
  puts params.inspect
}
Run Code Online (Sandbox Code Playgroud)

我想最好的是找到适用于普通路由和引擎的任意组合的通用解决方案,但这将适用于您的情况.

更新#2

要获得更一般的解决方案,您需要查看Rails路由器的来源,您可以在ActionDispatch模块中找到它.看看RoutingJourney模块.我发现recognize如果这是一个调度程序,可以测试从方法返回的路由.

request = ActionDispatch::Request.new(env)
Rails.application.routes.router.recognize(req) do |route, params|
  if route.dispatcher?
    # if this is a dispatcher, params should have everything you need
    puts params
  else
    # you need to go deeper
    # route.app.app will be Spree::Core::Engine
    route.app.app.routes.router.recognize(request) do |route, params|
      puts params.inspect
    }
  end
end
Run Code Online (Sandbox Code Playgroud)

这种方法适用于您的应用,但不一般.例如,如果你已经安装了sidekiq,route.app.appSidekiq::Web因此需要以不同的方式来处理.基本上要有一般解决方案,您需要处理Rails路由器支持的所有可安装引擎.

我想最好能够构建一些能够涵盖当前应用中所有情况的东西.所以要记住的是,当识别出初始请求时,route屈服于黑色的值可以是调度员.如果是,你有正常的Rails路由,如果不是,你需要递归检查.