LoginSignup
3
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Web API 混在の Rails アプリケーションで500エラーを適切に処理したい

Posted at

はじめに

Rails で、Web API でJSONを返すものと、HTMLのページを返すものを混在させたアプリケーションを rails new で作成していました。

routing で、Web API は、 /api 以下にマウントしていました。

/api/xxx で Web API が呼ばれて想定外のエラーが発生した時、JSON でレスポンスを返すようにしたいと考えたのですが、意外に手間取ったので自分用にメモしておきます。

なお、動作確認は、Rails7.1 で実施しています。

500エラーを発生させる

意図的に 500 エラーを発生させるために、Controller で以下のようなコードを書きます。

class Api::UsersController < ApplicationController
  def index
    raise StandardError, 'error'
  end
end

何もしない場合

何もしない場合、Rails は、/500.html を返します。JSON ではなくて、HTML が返ってきます。

ただし、リクエストするときに、HEADER で、Accept: application/json を指定している場合は、JSON が返ってきます。

curl -H "Accept: application/json" http://localhost:3000/api/users # => JSON
curl http://localhost:3000/api/users # => HTML

今回は、/api 以下のリクエストに対しては JSON を返すようにしたいです。

rescue_from StandardError は推奨されない

そこで、/api の場合だけ、 rescue_from を使って、StandardError をキャッチして JSON を返すようにしようと考えましたが、rescue_from で、 StandardError をキャッチするのは推奨されないということが Rails Guide に記載されていました(Rails Guide)。

config.exceptions_app を使う

別の方法を色々探していたのですが、config.exceptions_app を使う方法がいくつかみつかりました。

ちなみに、調べている途中で見つかったRambulance gem も config.exceptions_app を使っているようでした。

config.exceptions_app を試してみる

config/initializers/exceptions_app.rb を作成して以下のように記述します。

config/initializers/exceptions_app.rb
Rails.application.configure do
  config.exceptions_app = -> (env){ ErrorController.action(:show).call(env) }
end

app/controllers/error_controller.rb を作成して以下のように記述します。

app/controllers/error_controller.rb
class ErrorController < ApplicationController
  def show
    exception = request.env["action_dispatch.exception"]
    original_path = request.env["action_dispatch.original_path"]
    Rails.logger.error "Rendering 500 with exception in #{original_path}: #{exception.class.name}, message: (#{exception.message}), backtrace: #{exception.backtrace}"

    # /api 以下のリクエストの場合は、JSON を返す
    if original_path.start_with?("/api/")
      render_error_json(exception) and return
    end

    respond_to do |format|
      format.json { render_error_json(exception) }
      format.html { render file: Rails.root.join("public", "500.html"), status: :internal_server_error, layout: false }
    end
  end

  private

  def render_error_json(exception)
    render json: { result: "ng", message: exception.message }, status: :internal_server_error
  end
end

config.consider_all_requests_local を false にする

Development 環境では、config.consider_all_requests_localtrue になっていて、エラーが発生したときに、ErrorController が呼ばれないので、config/environments/development.rb に以下のように記述します。(これに気づくのに時間がかかりました。)

config/environments/development.rb
  config.consider_all_requests_local = false

試してみる

/api 以下のリクエストに対して、500エラーが発生した場合、JSON が返ってくることを確認します。

curl http://localhost:3000/api/users
{"result":"ng","message":"error"}

--api だとどうなるか?

ちなみに rails new --api でアプリケーションを作成した場合だと500エラーでもJSON が返ってきます。

まとめ

今回は、config.exceptions_app を使って、/api 以下のリクエストに対して、500エラーが発生した場合に JSON を返すようにしました。
他にもっといい方法があれば教えていただきたいです。

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0