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

时间:2017-03-09 09:40:38

标签: ruby-on-rails ruby ruby-on-rails-4 spree

Rails版本:'〜> 4.2.7.1'

狂欢版:'3.1.1'

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

详细说明:

我在rails应用程序中添加了一个类似于gem scout_statsd_rack的中间件。这会将以下middleware添加到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

我想要的是在中间件中找到当前路由,以便我可以发送特定于每条路由的指标。

我尝试了here描述的方法,它告诉env['PATH_INFO']可以提供路径,但是它提供了这样的URL参数:/api/products/4但我想要的是{{1}我的目的是跟踪/api/products/:id API的效果。

/api/products/:idenv['REQUEST_PATH']也会给出相同的回复。

我尝试了herehere提供的答案:

env['REQUEST_URI']

或者像这样

Rails.application.routes.router.recognize({"path_info" => env['PATH_INFO']})

但它出现了以下错误:

  

NoMethodError(未定义的方法Rails.application.routes.router.recognize(env['PATH_INFO']) find_routes'
  vendor / bundle / gems / actionpack-4.2.7.1 / lib / action_dispatch / journey / router.rb:59:in 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
call'

answer讨论了recognize'
vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
,但我如何访问变量request.original_url,我认为它应该与request相同但不能从此获取路由

编辑#1

您可以看到示例仓库here,其中包含rails中间件here的代码,可以按README中所述完成此设置,并且可以点击此API:{{ 1}}。

编辑#2

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

env

但我收到了以下回复:

http://localhost:3000/api/v1/products/1

我也推动了更改here

1 个答案:

答案 0 :(得分:9)

您需要将ActionDispatch::RequestRack::Request传递给recognize方法。以下是另一个应用程序的示例:

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

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

request = ActionDispatch::Request.new(env)

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

<强>更新

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

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

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

更新#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

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

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