Rails 版本:'~> 4.2.7.1'
spree 版本:'3.1.1'
提示:
我如何获得 /api/products/:id
的路线或在 Rails 4 应用程序的中间件中该路由的 Controller 和操作。
详情:
我正在我的 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']
也给出了相同的响应。
Rails.application.routes.router.recognize({"path_info" => env['PATH_INFO']})
或者像这样
Rails.application.routes.router.recognize(env['PATH_INFO'])
但它给出了以下错误:
NoMethodError (undefined method
path_info' for {"path_info"=>"/api/v1/products/4"}:Hash):<br/> 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:inrecognize'<br/> vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
call'
这answer讨论 request.original_url
, 但如何访问变量 request
, 我认为它应该与 env
相同但无法从中获得所需的路线。
编辑#1
您可以查看示例存储库 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 .
最佳答案
您需要将ActionDispatch::Request
或Rack::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
模块中找到它。查看 Routing
和 Journey
模块。我发现可以测试从 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
的值 yield to black 可以是调度程序,也可以不是。如果是,则您有正常的 Rails 路由,如果不是,则需要递归检查。
关于ruby-on-rails - 如何在 Rails 中间件中查找当前抽象路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42691729/