狂欢版:’3.1.1′
TlDr:
如何在Rails 4应用程序的中间件中获取路径as / 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,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 as我的目的是跟踪/ 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):
find_routes’
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:100:in
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:59:inrecognize'
call’
vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
这个answer讨论了request.original_url,但是我如何访问变量请求,我认为它应该与env相同但不能从中获取路由.
编辑#1
您可以看到示例仓库here,其中包含rails中间件here的代码,可以按照README中的说明完成此设置,并且可以命中此API:http:// localhost:3000 / api / v1 / products / 1.
编辑#2
我试过@MichałMłoźniak给出的方法如下:
- def call(env)
- (status,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.
解决方法
- 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)
如果您需要有关已识别路径的更多信息,可以通过识别方法查看要阻止的路径对象.
更新
上面的解决方案适用于普通的Rails路由,但由于你只安装了狂欢引擎,你需要使用不同的类
- request = ActionDispatch::Request.new(env)
- Spree::Core::Engine.routes.router.recognize(request) { |route,params|
- puts params.inspect
- }
我想最好的是找到适用于普通路由和引擎的任意组合的通用解决方案,但这将适用于您的情况.
更新#2
有关更一般的解决方案,您需要查看Rails路由器的源代码,您可以在ActionDispatch模块中找到它.查看路由和旅程模块.我发现如果这是一个调度员,可以测试来自识别方法的返回路由.
- 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路由器支持的所有可安装引擎.
我想最好能够构建一些能够涵盖当前应用中所有情况的东西.所以要记住的是,当识别出初始请求时,产生黑色的路由值可以是调度员.如果是,你有正常的Rails路由,如果不是,你需要递归检查.