無効なHTTPメソッドで405エラーが500になる問題を修正
Rails 7.2.3で無効なHTTPメソッドのリクエストが500エラーを返していた問題が修正されました。DebugExceptionsミドルウェアでの例外処理の二重発生を防ぐことで、適切な405 Method Not Allowedステータスが返されるようになります。
背景
Rails 7.1系では無効なHTTPメソッド(INVALID_METHODなど)のリクエストに対して405 Method Not Allowedを返していましたが、7.2.3では500 Internal Server Errorが発生するようになっていました。#56740がこの問題を報告しています。
問題の原因はDebugExceptionsミドルウェアでの例外処理の流れにありました。無効なHTTPメソッドのリクエストが到着すると、以下のような処理が発生していました:
- ルーターが
request.request_methodを呼び出し、UnknownHttpMethod例外が発生 -
DebugExceptionsが39行目で例外をキャッチ -
render_exceptionメソッド内でrequest.head?を呼び出し、再度request.methodにアクセス -
2回目の
UnknownHttpMethod例外が発生し、既存のrescueブロックをすり抜ける -
ShowExceptionsに送られるが、テスト環境のshow_exceptions: :none設定などでは500エラーとして処理される
この二重の例外発生により、本来405で処理されるべきリクエストが500エラーになっていました。
技術的な変更
actionpack/lib/action_dispatch/middleware/debug_exceptions.rbのrender_exceptionメソッドが修正され、request.head?の呼び出しがrequest.raw_request_methodに置き換えられました。
変更前:
if request.head?
render(wrapper.status_code, "", content_type)
elsif api_request?(content_type)
render_for_api_request(content_type, wrapper)
変更後:
if request.raw_request_method == "HEAD"
render(wrapper.status_code, "", content_type)
elsif api_request?(content_type)
render_for_api_request(content_type, wrapper)
request.head?は内部でrequest.methodを呼び出すため、無効なHTTPメソッドの場合に再度例外が発生していました。一方、raw_request_methodは生のHTTPメソッド文字列を返すメソッドで、バリデーションを行わないため例外を発生させません。
テストコードも修正され、実際に無効なHTTPメソッドでリクエストを送信するように変更されています:
# 変更前
get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => :all }
# 変更後
process :unknown, "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => :all }
getメソッドではなくprocessメソッドで:unknownシンボルを渡すことで、実際の無効なHTTPメソッドのシナリオを再現しています。
設計判断
メソッド呼び出しの置き換えという最小限の変更が選択されました。
同ファイルの63-66行目では既にInvalidType例外に対する保護処理が実装されていましたが、今回は例外ハンドリングを追加するのではなく、例外を発生させないメソッドへの置き換えが採用されています。これはhead?の判定が単純な文字列比較で実現できるため、バリデーション付きのメソッドを使う必要がないという判断です。
PR本文では4つのテストシナリオで動作確認が行われており、すべてのモード(開発/本番、show_exceptionsの各設定)で405が正しく返されることが検証されています。この変更により、Rails 7.1系との動作の一貫性が回復しました。
まとめ
本PRは、例外処理の二重発生を防ぐことで、無効なHTTPメソッドに対する正しいステータスコード応答を復元した修正です。request.head?からrequest.raw_request_methodへの置き換えという1行の変更で、ミドルウェアスタック全体の例外処理フローを安定化させています。