無効なHTTPメソッドで405エラーが500になる問題を修正

rails/rails

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メソッドのリクエストが到着すると、以下のような処理が発生していました:

  1. ルーターがrequest.request_methodを呼び出し、UnknownHttpMethod例外が発生
  2. DebugExceptionsが39行目で例外をキャッチ
  3. render_exceptionメソッド内でrequest.head?を呼び出し、再度request.methodにアクセス
  4. 2回目のUnknownHttpMethod例外が発生し、既存のrescueブロックをすり抜ける
  5. ShowExceptionsに送られるが、テスト環境のshow_exceptions: :none設定などでは500エラーとして処理される

この二重の例外発生により、本来405で処理されるべきリクエストが500エラーになっていました。

技術的な変更

actionpack/lib/action_dispatch/middleware/debug_exceptions.rbrender_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行の変更で、ミドルウェアスタック全体の例外処理フローを安定化させています。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の構成が非常に明確です。読者が変更の全体像から詳細までスムーズに理解できる優れた構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```ruby:ファイルパス)やGitHubのIssue・PRへのリンク記法が正しく使用されており、情報の参照性が高く評価できます。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Railsのミドルウェアやリクエスト処理に関する深い内容を扱っており、専門知識を持つエンジニアという対象読者に完全に適合しています。冗長な初心者向けの説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られています。非常に読みやすく、理解しやすい文章です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

actionpack/lib/action_dispatch/middleware/debug_exceptions.rbとactionpack/test/dispatch/debug_exceptions_test.rbの変更点が、提供されたDiffと完全に一致しています。コードの引用は正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「DebugExceptionsミドルウェア」「UnknownHttpMethod」「request.raw_request_method」など、Rails内部の技術用語を正確かつ適切な文脈で使用しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

例外が二重に発生するメカニズムや、`request.head?`と`request.raw_request_method`の挙動の違いについての説明は、技術的に正確かつ論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(問題の原因、解決策、テストシナリオなど)は、PRのDescriptionやDiffの内容によって裏付けられています。ハルシネーション(捏造)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#56750)、Issue番号(#56740)、バージョン番号(7.2.3, 7.1系)、PR Descriptionに記載の行番号など、すべての数値と固有名詞が正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「無効なHTTPメソッドで405エラーが500になる問題を修正」は、PRの主題を的確に要約しており、内容との整合性が取れています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事内で言及されているバージョン情報(Rails 7.2.3、7.1系)は、関連するIssueで言及されている内容と一致しており、背景説明を補強するための適切な引用です。PRに記載のない外部知識の捏造はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「Rails 7.1系では...だったが、7.2.3では...なっていた」といった時間的な前後関係の表現は、PRの文脈と一致しており正確です。時間表現の歪曲はありません。