2013-03-17 42 views
11

Rails 4增加了一个异常ActionDispatch :: ParamsParser :: ParseError异常,但由于它在中间件堆栈中出现,它不能在正常情况下获救控制器环境。在json API应用程序中,我想用标准错误格式进行响应。如何在Rails 4中从ActionDispatch :: ParamsParser :: ParseError救援

gist显示插入中间件拦截和响应的策略。根据这一模式,我有:

application.rb中:

module Traphos 
    class Application < Rails::Application 
    .... 
    config.middleware.insert_before ActionDispatch::ParamsParser, "JSONParseError" 
end 
end 

而中间件是:

class JSONParseError 
    def initialize(app) 
    @app = app 
    end 

    def call(env) 
    begin 
     @app.call(env) 
    rescue ActionDispatch::ParamsParser::ParseError => e 
     [422, {}, ['Parse Error']] 
    end 
    end 
end 

如果我跑我的测试,而不中间件我得到(SPEC) :

Failures: 

    1) Photo update attributes with non-parseable json 
    Failure/Error: patch update_url, {:description => description}, "CONTENT_TYPE" => content_type, "HTTP_ACCEPT" => accepts, "HTTP_AUTHORIZATION" => @auth 
    ActionDispatch::ParamsParser::ParseError: 
     399: unexpected token at 'description=Test+New+Description]' 

这正是我所期望的(ParseError,我不能rescue_from)。

与唯一的变化

我们在上面的中间件添加:

2) Photo update attributes with non-parseable json 
    Failure/Error: response.status.should eql(422) 

     expected: 422 
      got: 200 

和日志显示正在执行的标准控制器动作并返回正常应答(尽管因为它没有收到任何参数它没有更新任何东西)。

我的问题:

  1. 如何从ParseError营救,并返回一个自定义的响应。感觉像我在正确的轨道上,但不是那里。

  2. 我不能解决为什么,当异常被提出和救出时,控制器动作仍在继续。

帮助非常感谢,--Kip

+0

有没有其他一些中间件改变返回状态?你有没有用pry或其他方法进行调试? – phoet 2013-03-17 12:06:53

回答

6

原来,进一步向上中间件栈,ActionDispatch :: ShowExceptions可以与例外应用配置。

module Traphos 
    class Application < Rails::Application 
    # For the exceptions app 
    require "#{config.root}/lib/exceptions/public_exceptions" 
    config.exceptions_app = Traphos::PublicExceptions.new(Rails.public_path) 
    end 
end 

主要基于Rails的提供了一个我现在用:

module Traphos 
    class PublicExceptions 
    attr_accessor :public_path 

    def initialize(public_path) 
     @public_path = public_path 
    end 

    def call(env) 
     exception = env["action_dispatch.exception"] 
     status  = code_from_exception(env["PATH_INFO"][1..-1], exception) 
     request  = ActionDispatch::Request.new(env) 
     content_type = request.formats.first 
     body   = {:status => { :code => status, :exception => exception.class.name, :message => exception.message }} 
     render(status, content_type, body) 
    end 

    private 

    def render(status, content_type, body) 
     format = content_type && "to_#{content_type.to_sym}" 
     if format && body.respond_to?(format) 
     render_format(status, content_type, body.public_send(format)) 
     else 
     render_html(status) 
     end 
    end 

    def render_format(status, content_type, body) 
     [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", 
       'Content-Length' => body.bytesize.to_s}, [body]] 
    end 

    def render_html(status) 
     found = false 
     path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale 
     path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path)) 

     if found || File.exist?(path) 
     render_format(status, 'text/html', File.read(path)) 
     else 
     [404, { "X-Cascade" => "pass" }, []] 
     end 
    end 

    def code_from_exception(status, exception) 
     case exception 
     when ActionDispatch::ParamsParser::ParseError 
     "422" 
     else 
     status 
     end 
    end 
    end 
end 

要在测试环境中使用它需要设置配置变量(否则你得到标准的例外在开发和测试处理) 。因此,为了测试我已经(编辑只是有关键部位):

describe Photo, :type => :api do 
    context 'update' do 
    it 'attributes with non-parseable json' do 

     Rails.application.config.consider_all_requests_local = false 
     Rails.application.config.action_dispatch.show_exceptions = true 

     patch update_url, {:description => description} 
     response.status.should eql(422) 
     result = JSON.parse(response.body) 
     result['status']['exception'].should match(/ParseError/) 

     Rails.application.config.consider_all_requests_local = true 
     Rails.application.config.action_dispatch.show_exceptions = false 
    end 
    end 
end 

因为我需要在一个公共的API的方式执行,是适应于任何其他异常可能我选择定制。

0

本文(也来自2013)thoughtbot也涵盖此主题。他们把他们的回应放在这个中间件服务里面,只有当你要求json

if env['HTTP_ACCEPT'] =~ /application\/json/ 
    error_output = "There was a problem in the JSON you submitted: #{error}" 
    return [ 
     400, { "Content-Type" => "application/json" }, 
     [ { status: 400, error: error_output }.to_json ] 
    ] 
else 
raise error 
end