如何在 Rails 中间件中找到当前的抽象路由 [英] How to find current abstract route in Rails middware

查看:40
本文介绍了如何在 Rails 中间件中找到当前的抽象路由的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Rails 版本:'~> 4.2.7.1'

Rails version: '~> 4.2.7.1'

狂欢版本:'3.1.1'

Spree version: '3.1.1'

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

TlDr: How do I get route as /api/products/:id or controller and action of that route in a middleware of Rails 4 application.

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

I am adding a middleware in my rails app which is similar to gem scout_statsd_rack. This adds following middleware to rails app to send metrics via 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

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

What I want is to find current route in the middleware so that I can send metrics specific to each route.

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

I tried approach described here, which tells env['PATH_INFO'] can provide path, which it does, but it gives with URL params like this: /api/products/4 but what I want is /api/products/:id as my puropose is to track performance of /api/products/:id API.

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

env['REQUEST_PATH'] and env['REQUEST_URI'] also gives same response.

我尝试了此处这里:

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

或者像这样

Rails.application.routes.router.recognize(env['PATH_INFO'])

但它给出了以下错误:

NoMethodError(未定义方法 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
find_routes'
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:59:in recognize'
供应商/捆绑/宝石/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
调用'

NoMethodError (undefined method 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
find_routes'
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:59:in recognize'
vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
call'

这个 answer 讨论了 request.original_url,但我如何访问变量 request,我认为它应该与 env 相同,但无法从中获得所需的路由.

This answer discusses request.original_url, but How do I access variable request, I think it should be same as env but not able to get route as want from this.

您可以在此处查看示例存储库,其中包含 rails 中间件的代码 这里,这个设置可以按照 README 中的说明完成,而不是这个 API可以点击:http://localhost:3000/api/v1/products/1.

You can see the sample repo here, with code of rails middleware here, Setup of this can be done as stated in README and than this API can be hit: http://localhost:3000/api/v1/products/1.

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

I tried approach given by @MichałMłoźniak like following:

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
   }

但我得到了以下回复:

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=[]>>

我也推送了更改这里.

推荐答案

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

You need to pass ActionDispatch::Request or Rack::Request to recognize method. Here is an example from another app:

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.在中间件中,您可以轻松创建此对象:

The same will work with ActionDispatch::Request. Inside middleware, you can easily create this object:

request = ActionDispatch::Request.new(env)

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

And if you need more information about recognized route, you can look into that route object that is yielded to block, by recognize method.

更新

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

The above solution will work for normal Rails routes, but since you only have spree engine mounted you need to use different class

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

我想最好的办法是找到一个通用的解决方案,它适用于普通路线和引擎的任何组合,但这对你的情况有效.

I guess the best would be find a generic solution that works with any combination of normal routes and engines, but this will work in your case.

更新 #2

对于更通用的解决方案,您需要查看 Rails 路由器的源代码,您可以在 ActionDispatch 模块中找到它.查看RoutingJourney 模块.我发现从 recognize 方法返回的路由可以测试是否是调度程序.

For more general solution you need to look at the source of Rails router, which you can find in ActionDispatch module. Look at Routing and Journey modules. What I found out is that the returned route from recognize method can be tested if this is a dispatcher or not.

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 路由器支持的所有可能的可安装引擎.

This approach will work in case of your app, but will not be general. For example, if you have sidekiq installed, route.app.app will be Sidekiq::Web so it needs to be handled in different way. Basically to have general solution you need to handle all possible mountable engines that Rails router supports.

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

I guess it is better to build something that will cover all your cases in current application. So the thing to remember is that when initial request is recognized, the value of route yielded to black can be a dispatcher or not. If it is, you have normal Rails route, if not you need to recursive check.

这篇关于如何在 Rails 中间件中找到当前的抽象路由的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆