如何在基于JSON的RESTful代码中处理异常?

39

我有一个使用RESTful API进行JSON通讯的"软件即服务"应用程序。

简单地说:在使用RESTful API和JSON数据交换时,捕获和报告异常的最佳实践是什么?

我的第一个想法是查看Rails通过生成脚手架来做了什么,但那显然是不对的。这里是一个摘录:

class MumblesController < ApplicationController

  # GET /mumbles/1
  # GET /mumbles/1.json
  def show
    @mumble = Mumble.find(params[:id])
    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @mumble }
    end
  end

end
在这种情况下,如果JSON代码发送一个不存在的ID,例如:
http://www.myhost.com/mumbles/99999.json

然后Mumble.find()将会抛出ActiveRecord::RecordNotFound异常,ActionController会捕获该异常并以HTML格式呈现一个错误页面。但是对于期望JSON格式的客户端来说,HTML是无用的。

我可以通过将Mumble.find()放入begin ... rescue RuntimeError块中,并呈现一个JSON状态=> :unprocessable_entity或类似的内容来解决这个问题。

但如果客户端应用程序发送了无效路径,例如:

http://www.myhost.com/badtypo/1.json

一个基于 JSON 的应用是否应该捕获并返回 JSON 格式的错误信息?如果需要,我应该在哪里进行捕获而不需要深入到 ActionDispatch 中?

总的来说,如果出现错误,我是放弃让 ActionController 生成 HTML 还是做其他的处理?但这样做感觉不太对劲...

4个回答

78

我在提交问题前找到了答案。但这个答案也可能帮助到其他人...

使用ActionController的rescue_from

答案是使用ActionController的rescue_from,如此指南所述并文档化。特别地,你可以像下面这样替换默认的404.html和500.html文件的默认渲染:

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found

private
  def record_not_found(error)
    render :json => {:error => error.message}, :status => :not_found
  end 
end

1
注意:至少在Rails 4中,rescue_from中匹配的顺序与常规的rescue相反:如果您想要子类和其父类有不同的行为,请将子类放在父类之后 :-/ - AlexChaffee

7
作为一名开发者,你也会想要查看代码追踪信息(最好有有用的行,并过滤掉 gems)。并且在生产环境中要将代码追踪信息设置为不可见。
  rescue_from StandardError do |exception|
    # Handle only JSON requests
    raise unless request.format.json?

    err = {error: exception.message}

    err[:backtrace] = exception.backtrace.select do |line|
      # filter out non-significant lines:
      %w(/gems/ /rubygems/ /lib/ruby/).all? do |litter|
         not line.include?(litter)
      end
    end if Rails.env.development? and exception.is_a? Exception

    # duplicate exception output to console:
    STDERR.puts ['ERROR:', err[:error], '']
                    .concat(err[:backtrace] || []).join "\n"

    render :json => err, :status => 500
  end

7
如果有帮助的话,以下是我为我的纯json api添加的一些通用处理方法:
在每个特定控制器继承的应用控制器(ApplicationController)中添加以下内容:
# app/controllers/api/v1/application_controller.rb

# ...

rescue_from StandardError do |exception|
    render json: { :error => exception.message }, :status => 500
end

# ...
  • 主要基于fearless_fool的答案

1
对于任何被拯救的错误都响应500看起来有些过度了。 - Michael

1

如何保持编写JSON API代码的一致标准,目前还没有明确的共识,但以下是我实践的一些内容(比您要求的更多):

  1. 保持简单 - 尽量保持RESTful。自定义方法会使事情变得复杂。
  2. 让服务器返回本机错误代码,并使用'rescue_from'来捕获,并
  3. 在其他情况下,呈现Rails HTTP响应代码,这可以被客户端应用程序特别定位。

在您的情况下,您可能会发现Rails respond_to和respond_with会优雅地处理html/json/其他响应。即使在您的解决方案中,它仍将有效地呈现HTML,但这不是您的客户端应用程序所解释的内容,相反,它将读取HTTP头并获取HTTP响应代码,这就是触发'rescue_from'的原因。


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