【问题标题】:How to find current abstract route in Rails middware如何在 Rails 中间件中找到当前的抽象路由
【发布时间】:2017-07-30 05:23:46
【问题描述】:

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但我想要的是/api/products/:id,因为我的目的是跟踪/api/products/:id API 的性能。

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

我尝试了herehere提供的答案:

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'
vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
call'

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

编辑#1

您可以查看示例 repo here,其中包含 rails 中间件代码 here,可以按照 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
   }

但我得到以下回复:

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

我也推送了更改here

【问题讨论】:

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


    【解决方案1】:

    您需要将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 路线,但由于您只安装了 spree 引擎,因此您需要使用不同的类

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

    我想最好的办法是找到一个通用的解决方案,该解决方案适用于正常路线和引擎的任意组合,但这适用于您的情况。

    更新 #2

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

    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 路由,如果不是,你需要递归检查。

    【讨论】:

    • 感谢您的回复,我已经尝试了您的方法,但我没有得到您的答案中的控制器和操作,我得到了空哈希,我在问题本身中添加了代码更改和响应。
    • 谢谢,这行得通,您知道任何通用解决方案吗,因为我现在必须检查正常路线和狂欢路线。
    • @Saurabh 我已经添加了我的发现
    猜你喜欢
    • 2010-11-15
    • 2017-03-08
    • 1970-01-01
    • 2015-12-25
    • 2015-05-02
    • 1970-01-01
    • 2016-12-16
    • 2016-04-01
    • 1970-01-01
    相关资源
    最近更新 更多