如何在 rails 5 中挽救 ActionDispatch::ParamsParser::ParseError 和 return 自定义 API 错误?
How to rescue ActionDispatch::ParamsParser::ParseError and return custom API error in rails 5?
每当针对我的 API-only Rails 5.x 应用程序发送格式错误的 JSON 时,我都会收到异常并且 Rails 返回整个堆栈跟踪作为 JSON。显然,我想用一个漂亮的、自定义的、格式化的错误来回应。
=> Booting Puma
=> Rails 5.0.0.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.6.0 (ruby 2.3.0-p0), codename: Sleepy Sunday Serenity
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
Started POST "/api/v1/identities/" for ::1 at 2016-10-26 18:42:32 +0200
ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations".* FROM "schema_migrations"
Error occurred while parsing request parameters.
Contents:
{
"whatever": "ewewgewewtwe"
"malformed_json": ";
}
ActionDispatch::ParamsParser::ParseError (822: unexpected token at '{
"whatever": "ewewgewewtwe"
"malformed_json": ";
}'):
actionpack (5.0.0.1) lib/action_dispatch/http/parameters.rb:71:in `rescue in parse_formatted_parameters'
actionpack (5.0.0.1) lib/action_dispatch/http/parameters.rb:65:in `parse_formatted_parameters'
actionpack (5.0.0.1) lib/action_dispatch/http/request.rb:366:in `block in POST'
rack (2.0.1) lib/rack/request.rb:57:in `fetch'
rack (2.0.1) lib/rack/request.rb:57:in `fetch_header'
actionpack (5.0.0.1) lib/action_dispatch/http/request.rb:365:in `POST'
actionpack (5.0.0.1) lib/action_controller/metal/params_wrapper.rb:282:in `_wrapper_enabled?'
actionpack (5.0.0.1) lib/action_controller/metal/params_wrapper.rb:231:in `process_action'
activerecord (5.0.0.1) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
actionpack (5.0.0.1) lib/abstract_controller/base.rb:126:in `process'
actionpack (5.0.0.1) lib/action_controller/metal.rb:190:in `dispatch'
actionpack (5.0.0.1) lib/action_controller/metal.rb:262:in `dispatch'
actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:32:in `serve'
actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:39:in `block in serve'
actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:26:in `each'
actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:26:in `serve'
actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:725:in `call'
rack (2.0.1) lib/rack/etag.rb:25:in `call'
rack (2.0.1) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.1) lib/rack/head.rb:12:in `call'
activerecord (5.0.0.1) lib/active_record/migration.rb:552:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/callbacks.rb:38:in `block in call'
activesupport (5.0.0.1) lib/active_support/callbacks.rb:97:in `__run_callbacks__'
activesupport (5.0.0.1) lib/active_support/callbacks.rb:750:in `_run_call_callbacks'
activesupport (5.0.0.1) lib/active_support/callbacks.rb:90:in `run_callbacks'
actionpack (5.0.0.1) lib/action_dispatch/middleware/callbacks.rb:36:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/debug_exceptions.rb:49:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.0.0.1) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.0.0.1) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:70:in `block in tagged'
activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:70:in `tagged'
railties (5.0.0.1) lib/rails/rack/logger.rb:24:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/request_id.rb:24:in `call'
rack (2.0.1) lib/rack/runtime.rb:22:in `call'
activesupport (5.0.0.1) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/static.rb:136:in `call'
rack (2.0.1) lib/rack/sendfile.rb:111:in `call'
railties (5.0.0.1) lib/rails/engine.rb:522:in `call'
puma (3.6.0) lib/puma/configuration.rb:225:in `call'
puma (3.6.0) lib/puma/server.rb:578:in `handle_request'
puma (3.6.0) lib/puma/server.rb:415:in `process_client'
puma (3.6.0) lib/puma/server.rb:275:in `block in run'
puma (3.6.0) lib/puma/thread_pool.rb:116:in `block in spawn_thread'
以前我认为可以添加一个中间件并按如下方式处理异常:
# in config/application.rb
config.middleware.insert_before(ActionDispatch::ParamsError,'BadRequestError')
# middleware
class BadRequestError
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue ActionDispatch::ParamsParser::ParseError
Api::ApiController.action(:raise_bad_request).call(env)
end
end
end
不过中间件 ActionDispatch::ParamsError
似乎已从 Rails 5 中删除;
我也尝试使用其他中间件(例如 ActionDispatch::ShowExceptions),挽救了不同的错误,但我的 raise_bad_request
操作不知何故从未被调用。
我是不是遗漏了什么,做错了什么,或者有其他方法可以用 Rails 5 做到这一点?
谢谢!
更新了与 Rails 5.1 及更高版本一起使用的答案。 (感谢@Edwin Meyer)
config.middlware.use
现在需要 class,而不是字符串。
我现在还提到,我已将中间件放在 app/middleware
下。所以不需要要求。
我遇到了同样的问题,我的情况很容易解决。
只需在application.rb
中使用:
config.middleware.use CatchJsonParseErrors
# Instead of
# config.middleware.insert_before ActionDispatch::ParamsParser, "CatchJsonParseErrors"
# I used in my Rails 4 app
我的中间件看起来像这样(来自Catching Invalid JSON Parse Errors with Rack Middleware):
# app/middleware/catch_json_parse_errors.rb
class CatchJsonParseErrors
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue ActionDispatch::ParamsParser::ParseError => error
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
end
end
end
然后当我发送无效的 json 时,响应如下所示:
{"status":400,"error":"There was a problem in the JSON you submitted: 743: unexpected token at '{ \"foo\": \"bar\" '"}
您必须提供正确的 ACCEPT
header.
希望对你也有帮助。
我正在使用 Rails 2.6.3,我不得不这样做
# app/middleware/catch_json_parse_errors.rb
class CatchJsonParseErrors
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue ActionDispatch::Http::Parameters::ParseError => error
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
end
end
end
# config/application.rb
# require ...
# require middleware files
Dir["./app/middleware/*.rb"].each do |file|
require file
end
module Server
class Application < Rails::Application
# ...
config.autoloader = :classic # avoid zeitwerk loading issue in production
config.middleware.use CatchJsonParseErrors
每当针对我的 API-only Rails 5.x 应用程序发送格式错误的 JSON 时,我都会收到异常并且 Rails 返回整个堆栈跟踪作为 JSON。显然,我想用一个漂亮的、自定义的、格式化的错误来回应。
=> Booting Puma
=> Rails 5.0.0.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.6.0 (ruby 2.3.0-p0), codename: Sleepy Sunday Serenity
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
Started POST "/api/v1/identities/" for ::1 at 2016-10-26 18:42:32 +0200
ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations".* FROM "schema_migrations"
Error occurred while parsing request parameters.
Contents:
{
"whatever": "ewewgewewtwe"
"malformed_json": ";
}
ActionDispatch::ParamsParser::ParseError (822: unexpected token at '{
"whatever": "ewewgewewtwe"
"malformed_json": ";
}'):
actionpack (5.0.0.1) lib/action_dispatch/http/parameters.rb:71:in `rescue in parse_formatted_parameters'
actionpack (5.0.0.1) lib/action_dispatch/http/parameters.rb:65:in `parse_formatted_parameters'
actionpack (5.0.0.1) lib/action_dispatch/http/request.rb:366:in `block in POST'
rack (2.0.1) lib/rack/request.rb:57:in `fetch'
rack (2.0.1) lib/rack/request.rb:57:in `fetch_header'
actionpack (5.0.0.1) lib/action_dispatch/http/request.rb:365:in `POST'
actionpack (5.0.0.1) lib/action_controller/metal/params_wrapper.rb:282:in `_wrapper_enabled?'
actionpack (5.0.0.1) lib/action_controller/metal/params_wrapper.rb:231:in `process_action'
activerecord (5.0.0.1) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
actionpack (5.0.0.1) lib/abstract_controller/base.rb:126:in `process'
actionpack (5.0.0.1) lib/action_controller/metal.rb:190:in `dispatch'
actionpack (5.0.0.1) lib/action_controller/metal.rb:262:in `dispatch'
actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:32:in `serve'
actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:39:in `block in serve'
actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:26:in `each'
actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:26:in `serve'
actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:725:in `call'
rack (2.0.1) lib/rack/etag.rb:25:in `call'
rack (2.0.1) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.1) lib/rack/head.rb:12:in `call'
activerecord (5.0.0.1) lib/active_record/migration.rb:552:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/callbacks.rb:38:in `block in call'
activesupport (5.0.0.1) lib/active_support/callbacks.rb:97:in `__run_callbacks__'
activesupport (5.0.0.1) lib/active_support/callbacks.rb:750:in `_run_call_callbacks'
activesupport (5.0.0.1) lib/active_support/callbacks.rb:90:in `run_callbacks'
actionpack (5.0.0.1) lib/action_dispatch/middleware/callbacks.rb:36:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/debug_exceptions.rb:49:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.0.0.1) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.0.0.1) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:70:in `block in tagged'
activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:70:in `tagged'
railties (5.0.0.1) lib/rails/rack/logger.rb:24:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/request_id.rb:24:in `call'
rack (2.0.1) lib/rack/runtime.rb:22:in `call'
activesupport (5.0.0.1) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.0.0.1) lib/action_dispatch/middleware/static.rb:136:in `call'
rack (2.0.1) lib/rack/sendfile.rb:111:in `call'
railties (5.0.0.1) lib/rails/engine.rb:522:in `call'
puma (3.6.0) lib/puma/configuration.rb:225:in `call'
puma (3.6.0) lib/puma/server.rb:578:in `handle_request'
puma (3.6.0) lib/puma/server.rb:415:in `process_client'
puma (3.6.0) lib/puma/server.rb:275:in `block in run'
puma (3.6.0) lib/puma/thread_pool.rb:116:in `block in spawn_thread'
以前我认为可以添加一个中间件并按如下方式处理异常:
# in config/application.rb
config.middleware.insert_before(ActionDispatch::ParamsError,'BadRequestError')
# middleware
class BadRequestError
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue ActionDispatch::ParamsParser::ParseError
Api::ApiController.action(:raise_bad_request).call(env)
end
end
end
不过中间件 ActionDispatch::ParamsError
似乎已从 Rails 5 中删除;
我也尝试使用其他中间件(例如 ActionDispatch::ShowExceptions),挽救了不同的错误,但我的 raise_bad_request
操作不知何故从未被调用。
我是不是遗漏了什么,做错了什么,或者有其他方法可以用 Rails 5 做到这一点?
谢谢!
更新了与 Rails 5.1 及更高版本一起使用的答案。 (感谢@Edwin Meyer)
config.middlware.use
现在需要 class,而不是字符串。
我现在还提到,我已将中间件放在 app/middleware
下。所以不需要要求。
我遇到了同样的问题,我的情况很容易解决。
只需在application.rb
中使用:
config.middleware.use CatchJsonParseErrors
# Instead of
# config.middleware.insert_before ActionDispatch::ParamsParser, "CatchJsonParseErrors"
# I used in my Rails 4 app
我的中间件看起来像这样(来自Catching Invalid JSON Parse Errors with Rack Middleware):
# app/middleware/catch_json_parse_errors.rb
class CatchJsonParseErrors
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue ActionDispatch::ParamsParser::ParseError => error
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
end
end
end
然后当我发送无效的 json 时,响应如下所示:
{"status":400,"error":"There was a problem in the JSON you submitted: 743: unexpected token at '{ \"foo\": \"bar\" '"}
您必须提供正确的 ACCEPT
header.
希望对你也有帮助。
我正在使用 Rails 2.6.3,我不得不这样做
# app/middleware/catch_json_parse_errors.rb
class CatchJsonParseErrors
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue ActionDispatch::Http::Parameters::ParseError => error
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
end
end
end
# config/application.rb
# require ...
# require middleware files
Dir["./app/middleware/*.rb"].each do |file|
require file
end
module Server
class Application < Rails::Application
# ...
config.autoloader = :classic # avoid zeitwerk loading issue in production
config.middleware.use CatchJsonParseErrors