Rails 4.0中的自定义错误处理

16

我正在使用 Ruby 2.0 和 Rails 4.0 构建一个基于 Ruby on Rails 的 API。我的应用几乎完全是一个 JSON API,因此如果发生错误(500、404),我希望能够捕获该错误并返回格式良好的 JSON 错误消息。

我尝试过 这个,以及:

rescue_from ActionController::RoutingError, :with => :error_render_method

def error_render_method
  puts "HANDLING ERROR"
  render :json => { :errors => "Method not found." }, :status => :not_found
  true
end

在我的 ApplicationController 中。

这两个方法都没有起作用(异常根本没有被捕获)。我查了很多资料发现在 Rails 3.1、3.2 和 4.0 中的实现方式有很大不同,但是我找不到任何关于在 Rails 4.0 中如何实现的好文档。

有人知道吗?

编辑:当我进入404页面时,以下是堆栈跟踪:

Started GET "/testing" for 127.0.0.1 at 2013-08-21 09:50:42 -0400

ActionController::RoutingError (No route matches [GET] "/testing"):
actionpack (4.0.0) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'
actionpack (4.0.0) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
railties (4.0.0) lib/rails/rack/logger.rb:38:in `call_app'
railties (4.0.0) lib/rails/rack/logger.rb:21:in `block in call'
activesupport (4.0.0) lib/active_support/tagged_logging.rb:67:in `block in tagged'
activesupport (4.0.0) lib/active_support/tagged_logging.rb:25:in `tagged'
activesupport (4.0.0) lib/active_support/tagged_logging.rb:67:in `tagged'
railties (4.0.0) lib/rails/rack/logger.rb:21:in `call'
actionpack (4.0.0) lib/action_dispatch/middleware/request_id.rb:21:in `call'
rack (1.5.2) lib/rack/methodoverride.rb:21:in `call'
rack (1.5.2) lib/rack/runtime.rb:17:in `call'
activesupport (4.0.0) lib/active_support/cache/strategy/local_cache.rb:83:in `call'
rack (1.5.2) lib/rack/lock.rb:17:in `call'
actionpack (4.0.0) lib/action_dispatch/middleware/static.rb:64:in `call'
railties (4.0.0) lib/rails/engine.rb:511:in `call'
railties (4.0.0) lib/rails/application.rb:97:in `call'
rack (1.5.2) lib/rack/lock.rb:17:in `call'
rack (1.5.2) lib/rack/content_length.rb:14:in `call'
rack (1.5.2) lib/rack/handler/webrick.rb:60:in `service'
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/webrick/httpserver.rb:138:in `service'
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/webrick/httpserver.rb:94:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/webrick/server.rb:295:in `block in start_thread'


Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.0ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/routes/_route.html.erb (2.9ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/routes/_route.html.erb (0.9ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/routes/_table.html.erb (1.1ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (38.3ms)

我不认为我希望它进展到这一步,应该有东西拦截它并返回适当的 JSON 错误响应。


嗯,这可能实际上是按照我设置的方式在工作……让我再测试一下。 - Ethan Mick
5个回答

17

这个请求甚至没有到达你的应用程序。

你需要定义一个万能路由,这样Rails才会将请求发送到你的应用程序,而不是显示一个错误(在开发中)或呈现public/404.html页面(在生产中)。

修改你的routes.rb文件,包含以下内容:

match "*path", to: "errors#catch_404", via: :all

你的控制器代码如下

class ErrorsController < ApplicationController

  def catch_404
    raise ActionController::RoutingError.new(params[:path])
  end
end

你的rescue_from应该捕获这个错误。


1
你可能想要将路由中的 get 更改为 match,因为其他请求方法也可能发生这种情况。 - dylanfm
1
Rails 4会提醒你“将你的操作暴露给GET和POST”,通过添加via: [:get, :post]来实现 - 记得也要这样做! :) - sameers

16

在尝试了几种不同的方法后,我已经确定这是处理API 404错误最简单的方式:

# Passing request spec
describe 'making a request to an unrecognised path' do
  before { host! 'api.example.com' }
    it 'returns 404' do
    get '/nowhere'
    expect(response.status).to eq(404)
  end
end

# routing
constraints subdomain: 'api' do
  namespace :api, path: '', defaults: { format: 'json' } do
    scope module: :v1, constraints: ApiConstraints.new(1) do
      # ... actual routes omitted ...
    end
    match "*path", to: -> (env) { [404, {}, ['{"error": "not_found"}']] }, via: :all
  end
end

1
+1 保持在路由文件中并添加测试。我在我的应用程序中几乎原封不动地使用了这个,谢谢! - Joost Baaij

9

这个方法在Rails4中可用,使用这种方式可以直接管理所有错误:例如,当API调用发生错误时,你可以将error_info呈现为JSON格式。

application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery


  # CUSTOM EXCEPTION HANDLING
  rescue_from StandardError do |e|
    error(e)
  end

  def routing_error
    raise ActionController::RoutingError.new(params[:path])
  end

  protected

  def error(e)
    #render :template => "#{Rails::root}/public/404.html"
    if env["ORIGINAL_FULLPATH"] =~ /^\/api/
    error_info = {
      :error => "internal-server-error",
      :exception => "#{e.class.name} : #{e.message}",
    }
    error_info[:trace] = e.backtrace[0,10] if Rails.env.development?
    render :json => error_info.to_json, :status => 500
    else
      #render :text => "500 Internal Server Error", :status => 500 # You can render your own template here
      raise e
    end
  end

  # ...

end

routes.rb

MyApp::Application.routes.draw do

  # ...

  # Any other routes are handled here (as ActionDispatch prevents RoutingError from hitting ApplicationController::rescue_action).
  match "*path", :to => "application#routing_error", :via => :all
end

2
我使用了public文件夹中的404.html,并且这是在开发环境下。实际上,我从以下两个网站得到了答案:

然而,我进行了一些实验来确定哪些代码片段可以使其正常工作。以下是我添加的代码片段:

config/routes.rb

Rails.application.routes.draw do
    // other routes
    match "*path", to: "application#catch_404", via: :all
end

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
    def catch_404
        render :file => 'public/404.html', :status => :not_found
    end
end

希望您能对以下原代码为何需要进行说明和澄清。例如,使用该行代码:

raise ActionController::RoutingError.new(params[:path])

并且这个

rescue_from ActionController::RoutingError, :with => :error_render_method

因为在旧版本的Rails中,rescue_fromraise ActionController::RoutingError似乎是常见的解决方案。


0
如果您想以相同的方式响应所有类型的错误,请尝试以下操作: rescue_from StandardError, :with => :error_render_method 如果您不希望在开发模式下出现此行为,请将上述代码添加到以下位置: unless Rails.application.config.consider_all_requests_local

1
好知道,但看起来这根本没有捕捉错误(我添加了一个堆栈跟踪)。我的“error_render_method”从未被调用。 - Ethan Mick
我正在使用 Rails 3.2.13 并且它对我有效。也许在 Rails 4 中他们围绕这个做了一些改变。 - usha
如果您在ActionController中包含了错误的内容,那么在几种不同的情况下就不会被调用。根据文档:“如果任何异常.is_a?(klass)为真,则调用第一个类的处理程序”。 - WattsInABox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接