[英]How to Rescue from ActionDispatch::ParamsParser::ParseError in Rails 4
Rails 4 adds an exception ActionDispatch::ParamsParser::ParseError exception but since its in the middleware stack it appears it can't be rescued in the normal controller environment. Rails 4添加了一个异常ActionDispatch :: ParamsParser :: ParseError异常,但由于它在中间件堆栈中,它似乎无法在普通控制器环境中获救。 In a json API application I want respond with a standard error format. 在json API应用程序中,我想用标准错误格式进行响应。
This gist shows a strategy for inserting middleware to intercept and respond. 这个要点显示了插入中间件以拦截和响应的策略。 Following this pattern I have: 遵循这种模式我有:
application.rb: application.rb中:
module Traphos
class Application < Rails::Application
....
config.middleware.insert_before ActionDispatch::ParamsParser, "JSONParseError"
end
end
And the middleware is: 中间件是:
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
If I run my test without the middleware I get (spec): 如果我在没有中间件的情况下运行测试,我会得到(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]'
Which is exactly what I would expect (ParseError that I can't rescue_from). 这正是我所期望的(ParseError,我无法rescue_from)。
Now with the only change to add in the middleware above: 现在只需在上面的中间件中添加更改:
2) Photo update attributes with non-parseable json
Failure/Error: response.status.should eql(422)
expected: 422
got: 200
And the log shows that the standard controller action is being executed and returning a normal response (although since it didn't receive any parameters it didn't update anything). 并且日志显示正在执行标准控制器操作并返回正常响应(尽管因为它没有接收任何参数而没有更新任何内容)。
My questions: 我的问题:
How can rescue from ParseError and return a custom response. 如何从ParseError中解救并返回自定义响应。 Feels like I'm on the right track but not quite there. 感觉就像我在正确的轨道,但不是那里。
I can't work out why, when the exception is raised and rescued, that the controller action still proceeds. 我无法弄清楚为什么,当异常被提出并获救时,控制器动作仍然继续进行。
Help much appreciated, --Kip 非常感谢, - Kip
Turns out that further up the middleware stack, ActionDispatch::ShowExceptions can be configured with an exceptions app. 事实证明,在中间件堆栈中,可以使用例外应用程序配置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
Based heavily on the Rails provided one I am now using: 基于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
To use it in a test environment requires setting config variables (otherwise you get the standard exception handling in development and test). 要在测试环境中使用它,需要设置配置变量(否则您将在开发和测试中获得标准异常处理)。 So to test I have (edited to just have the key parts): 所以测试我有(编辑到只有关键部分):
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
Which performs as I need in a public API way and is adaptable for any other exceptions I may choose to customise. 它以我需要的公共API方式执行,并且适用于我可能选择自定义的任何其他异常。
This article (also from 2013) thoughtbot covers also this topic. 这篇文章(也是从2013年开始) 思想机构也涵盖了这一主题。 They put their response inside this middleware service only if you requested json 只有在您请求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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.