1. kenchan0130

    No comment

    kenchan0130
Changes in body
Source | HTML | Preview
@@ -1,123 +1,123 @@
Rails 5 で [rails-api](https://github.com/rails-api/rails-api) が[公式に取り込まれる](https://github.com/rails/rails/pull/19832)ことになり、ますます Rails で API アプリケーションを書くケースが増えていくことでしょう。
ほとんどのケースでデータのやり取りは JSON を介して行われると思いますが、
Rails はその辺り(主にリクエスト周り)をよろしくやってくれるのでとても助かっています。
さて、このエントリでは JSON のリクエストパラメータがパース出来なかった事案に遭遇したので、対応方法を書いていこうと思います。
後述していますが、 Rails 5 系ではこの対応は必要なくなる予定です。
## 対応方法
```rb:config/initializers/rescue_json_parse_errors.rb
# Rails 4 系の場合
class RescueJsonParseErrors
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue ActionDispatch::ParamsParser::ParseError => _e
return [
400, { 'Content-Type' => 'application/json' },
[{ error: 'There was a problem in the your JSON' }.to_json]
]
end
end
end
# Rails 3 系の場合
class RescueJsonParseErrors
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue MultiJson::LoadError => _e
return [
400, { 'Content-Type' => 'application/json' },
[{ error: 'There was a problem in the your JSON' }.to_json]
]
end
end
end
```
```rb:config/application.rb
module MyApp
class Application < Rails::Application
config.middleware.insert_before ActionDispatch::ParamsParser, 'RescueJsonParseErrors'
end
end
```
今回は `initializers` にファイルを設置していますが、 application が立ち上がる際に `require` されていれば良いので、ご自身の構成に合わせて適宜変更していただいて構いません。
また、必ずしもファイル名やクラス名がこの名前である必要はありません。
上記のようにすると JSON パースが出来なかった場合、
HTTP status を 400 として json レスポンスを返却することができます。
`env` は rack の `env` なので、
`env['HTTP_ACCEPT']` の内容で レスポンスの `Content-Type` を変更するもよし、 remote IP などをログに吐くもよしです。
## そもそもなぜこんなことをやる必要があるのか
これって
```rb
# Rails 4 系
-# grape の場合
+## grape の場合
class API < Grape::API
rescue_from ActionDispatch::ParamsParser::ParseError do
error!('There was a problem in the your JSON', 400)
end
end
-# action controller の場合
+## action controller の場合
class ApplicationController < ActionController::Base
rescue_from ActionDispatch::ParamsParser::ParseError do
render json: { message: 'There was a problem in the your JSON' }, status: 400
end
end
# Rails 3 系
-# grape の場合
+## grape の場合
class API < Grape::API
rescue_from MultiJson::LoadError do
error!('There was a problem in the your JSON', 400)
end
end
-# action controller の場合
+## action controller の場合
class ApplicationController < ActionController::Base
rescue_from MultiJson::LoadError do
render json: { message: 'There was a problem in the your JSON' }, status: 400
end
end
```
みたいにすればいいんじゃないの?
と思われる方もいるかもしれませんが、これでは `MultiJson::LoadError` や `ActionDispatch::ParamsParser::ParseError` を捕捉することができません。
なぜなら、(まぁ対応している通りですが) controller などで使用できる `params` の parse が controller などに到達する前に行われているためです。
具体的には Rails 3 系なら[ここ](https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/middleware/params_parser.rb#L46-L50)、 Rails 4 系なら[ここ](https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/params_parser.rb#L43-L46)です。
## ハマったところ
```rb:config/application.rb
config.middleware.insert_before ActionDispatch::ParamsParser, RescueJsonParseErrors.to_s
```
としたら `uninitialized constant RescueJsonParseErrors` で怒られました。
それもそのはずで、 `config/application.rb` が読み込まれた後に `initializers` のファイルが読み込まれるからです。
素直に初めから文字列にするか、 `application.rb` で `RescueJsonParseErrors` を定義してあるファイルを `require` すればよかったのでした。
## 余談
あくまで個人的な意見ですが、サーバー側は強固に越したことはないので、 Rails 3 系 および 4系 で API アプリケーションを作成する場合は、この対応は入れた方が良いと思っています。
また、Rails 5 系ではこの辺り修正が入るようです。
[ここ](https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L372-L374)を見る限り、 `ParamsParser::ParseError` が raise されてくるので、 controller などで `ParamsParser::ParseError` が捕捉できるようになりますね。