Help us understand the problem. What is going on with this article?

Rails で JSON のリクエストパラメータがパース出来なかった場合の対応

More than 3 years have passed since last update.

Rails 5 で rails-api公式に取り込まれることになり、ますます Rails で API アプリケーションを書くケースが増えていくことでしょう。
ほとんどのケースでデータのやり取りは JSON を介して行われると思いますが、
Rails はその辺り(主にリクエスト周り)をよろしくやってくれるのでとても助かっています。

さて、このエントリでは JSON のリクエストパラメータがパース出来なかった事案に遭遇したので、対応方法を書いていこうと思います。
後述していますが、 Rails 5 系ではこの対応は必要なくなる予定です。

対応方法

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

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 などをログに吐くもよしです。

そもそもなぜこんなことをやる必要があるのか

これって

# Rails 4 系
## 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 の場合
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 の場合
class API < Grape::API
  rescue_from MultiJson::LoadError do
     error!('There was a problem in the your JSON', 400)
  end
end

## 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::LoadErrorActionDispatch::ParamsParser::ParseError を捕捉することができません。

なぜなら、(まぁ対応している通りですが) controller などで使用できる params のパースが controller などに到達する前に行われているためです。
具体的には Rails 3 系ならここ、 Rails 4 系ならここです。

ハマったところ

config/application.rb
config.middleware.insert_before ActionDispatch::ParamsParser, RescueJsonParseErrors.to_s

としたら uninitialized constant RescueJsonParseErrors で怒られました。
それもそのはずで、 config/application.rb が読み込まれた後に initializers のファイルが読み込まれるからです。
素直に初めから文字列にするか、 application.rbRescueJsonParseErrors を定義してあるファイルを require すればよかったのでした。

Rails 5

Rails 5.0.0.rc1 現在ではこの辺り修正が入るようです。
ここを見る限り、 ParamsParser::ParseError が raise されてくるので、 controller などで ParamsParser::ParseError が捕捉できるようになりますね。

余談

例えばどういうケースで恩恵が得られるのでしょうか。
ログインを行う API を提供するケースを考えてみましょう。
何かしらの理由で JSON がパースできなかった際、
ActionDispatch::Http::FilterParameters が適用されずメールアドレスやパスワードなどがログに出力される自体が考えられます。

また、あくまで個人的な意見ですが、サーバー側は強固に越したことはないので、 Rails 3 系 および 4系 で API アプリケーションを作成する場合は、この対応は入れた方が良いと思っています。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away