405
パスは存在しますし、サーバーはそのメソッド自体も知っています。ただ、ここではそのメソッドを受け付けない——そして受け付けるメソッドの一覧を渡すことが義務づけられています。
同じ 3 桁でも、リクエストのどちら側にいるかで別々の問題になります。自分に当てはまるブロックを読んでください。
これが出ている理由
ブラウザでいちばんよく出る 405 は、自分では一度も書いていないほうです。JSON の content type やカスタムヘッダーを載せたオリジン跨ぎのリクエストはプリフライトされるので、ブラウザはまず `OPTIONS` を送ります。ルーターに GET と POST しか登録していないサーバーは、そのプリフライトに 405 で答えます。するとコンソールに出るのは CORS エラーです。プリフライトが成功しなかったからで、その CORS のメッセージが下にある 405 を隠してしまうため、ルートが無いだけの問題に `Access-Control-Allow-Origin` を睨んで何時間も使うことになります。2 つ目の形は、GET として届く POST です。RFC 9110 §15.4.2 は、ユーザーエージェントが 301 や 302 を辿るとき歴史的に POST を GET へ書き換えてきたと記録しています。だから `http://` の URL に投げたフォームが `https://` へリダイレクトされたり、末尾スラッシュを足すパスへリダイレクトされたりすると、サーバーにはまったく別のメソッドとして到着します。
やること
何よりも先に、ネットワークパネルで失敗したリクエストのメソッドを読んでください。呼んだつもりのメソッドではないことがよくあります。OPTIONS と出ているなら作業はサーバー側です——プリフライトには CORS ヘッダーと 204 で答える——ので、`fetch` をどういじっても解決しません。次に 405 自身の `allow` ヘッダーを読みます。§15.5.6 が必須としているもので、そのパスが実際に受け付けるメソッドの直接的な表明なので、たいていその場で調査が終わります。経路にリダイレクトがあるなら、最終的な URL へ直接送ってください。POST になりうるものには 307 か 308 を使うサーバーを選ぶこと。メソッドを保つリダイレクトはこの 2 つだけです。最後に、直したあとはエッジをパージしてください。405 がキャッシュされている可能性は現実にあります。
これが出ている理由
ルーターがパスには一致してメソッドには一致しなかったか、途中で誰かがどちらかを書き換えたかです。この状況の伝え方はフレームワークごとにばらばらで、正しい `Allow` を付けて 405 を返すものもあれば、一致しないメソッドに 404 を返すものもあります。だから同一のバグが、自社の 2 つのサービスで別々の問題に見えます。静的ファイルの配信はそれ自体が一つの場合です。nginx の static モジュールは GET と HEAD しか扱わないので、ディスク上のファイルに解決されるパスへの POST は、アプリケーションではなく nginx から `405 Not Allowed` として返ります。リクエストがアプリケーションのログに一切現れない理由がこれです。残りはパスの書き換えです。末尾スラッシュを足したり削ったりする rewrite は POST を GET 専用のルートへ移動させえますし、`/api/users` を `/api/users/` へリダイレクトするプロキシは、その途中でクライアントにメソッドを落とす機会を与えます。
やること
ルートテーブルを出力して、メソッドとパスを 2 つの別々の事実ではなく組として比べてください。どのフレームワークにもそのコマンドがありますし、ルーターのコードを読むより速いです。自分の 405 が本当に `Allow` を運んでいるかを確認してください。MUST であり、その応答が言えることの中でいちばん有用な一言です。省くフレームワークは、クライアント側で 1 分で終わる修正をサポートのやり取りに変えています。特に、`HEAD` や `OPTIONS` に誤って 405 を返していないかを見てください。§9.1 は汎用サーバーに GET と HEAD を要求していますし、OPTIONS はすべてのブラウザのプリフライトが使うものなので、この 2 つはテストしていない何かを壊す確率がいちばん高いところです。405 がアプリケーションのログに一度も届かないなら、見るべきは静的ファイルのハンドラと rewrite ルールです。そして後で CDN をパージすること。§15.1 によってこの応答はヒューリスティックにキャッシュ可能です。
これが出ている理由
これをあなたが引き起こした可能性はかなり低いです。たいていはフォームを送信したあとに出てきて、確認画面の代わりに短いエラーページが出ます。意味するところは、サイトのフォームが送信を受け付けないアドレスに向いている、ということです。古いブックマークや、`www.` 付きと無しを切り替えるリンクを辿ったあとに出ることもあります。途中のリダイレクトが、送信を普通のページ表示に変えてしまうからです。ブラウザにも端末にも、入力した内容にも問題はありません。
やること
エラーページをリロードしないでください。リロードは拒否された同じ送信をもう一度送るだけで、同じように失敗しますし、ブラウザによっては再送信の警告も出ます。フォームに戻り、`https://` でサイトのトップページから入り直して、途中でリダイレクトが起きない状態で 1 回だけ送信してください。サイトに `www.` 付きと無しの両方のアドレスがあるなら、サイト自身のリンクが使っているほうに合わせます。それでも失敗するなら、これはサイト側の配線ミスです。伝えるなら、どのページにいて、どのボタンを押したのかが役に立ちます。
失敗した URL に対してそのまま実行してください。エラーページを表示せずにステータスだけを出すので、ブラウザが描画したものではなくサーバーが言ったことが見えます。
curl -sS -X POST -o /dev/null -D - -w '\n%{http_code}\n' https://example.com/`-X POST` は curl が本来使うメソッドを置き換え、`-o /dev/null` はエラーページを捨て、`-D -` が応答ヘッダーを落とします。答えはそこにあります。探すヘッダーは `allow` です。§15.5.6 が 405 に必須としていて、そのパスが本当に受け付けるメソッドを列挙するので、1 回実行すれば呼び方が悪いのかルートが悪いのかが分かります。無いこと自体も発見として読んでください。`Allow` の無い 405 は仕様どおりに答えていないサーバーですし、`Allow` があって値が空なら、そのリソースは何も受け付けないという合法な表明です。数秒を足す価値のある変種が 2 つあります。`curl -I` は GET ではなく HEAD を送るので、`-I` で得た 405 は HEAD だけの話で、本当に気にしているメソッドの話ではないかもしれません。§9.1 に照らせば、GET が通るのに HEAD で 405 が出るのはサーバーの欠陥です。そしてブラウザのプリフライトを推測せずに再現するには `curl -sS -D - -o /dev/null -X OPTIONS -H 'origin: https://app.example.com' -H 'access-control-request-method: POST' https://example.com/api/` を実行します。ここで出る 405 が、それを一言も名指ししない CORS メッセージの本当の原因です。
RFC 9110 §15.5.6 は 405 を、リクエスト行のメソッドをオリジンサーバーは知っているが、対象リソースがそれをサポートしていない状態と定義し、役に立つほうの半分を必須にしています。405 を生成するオリジンサーバーは、その対象リソースが現在サポートしているメソッドを並べた `Allow` ヘッダーフィールドを生成しなければならない (MUST)、と。`Allow` の定義は §10.2.1 にあり、読み違える前に知っておくべき細部が 1 つあります。`Allow` の値が空であることは合法で、それはリソースがどのメソッドもサポートしないという意味です。ヘッダーが存在しないことと同じではありません。意味の輪郭は隣の 2 つのコードが決めています。§15.6.2 は 501 (Not Implemented) を、どのリソースに対してもサーバーがサポートしていないメソッド用に取ってあるので、405 のほうが狭い主張です——このメソッド自体は知っている、ただしここでは駄目、ということ。そして §9.1 は、汎用のサーバーがすべて GET と HEAD をサポートすることを求めています。だから GET が通るリソースへの HEAD に 405 を返すのはポリシーではなく欠陥です。これは覚えておく価値があります。`curl -I` は HEAD を送るので、最初に手が伸びる道具が、調査対象のコードそのものを作り出しかねません。最後の性質はデプロイより長生きします。405 は RFC 9110 §15.1 がヒューリスティックにキャッシュ可能と挙げるステータスコードの一つで、404・410・501 と同じ並びにいます。つまり `Cache-Control` が一切無くてもキャッシュや CDN が保存してかまいません。まずいロールアウトの 10 分間だけ 405 を返していたルートが、ロールアウトを直したあともエッジから 405 を返し続ける——保存された応答が期限切れになるか誰かがパージするまで。404 と同じ罠で、理由も同じです。
| 紛らわしいコード | 見分け方 |
|---|---|
501 | 501 (Not Implemented, §15.6.2) は、どのリソースに対してもサーバーがそのメソッドをサポートしていないという意味です。405 はメソッド自体はサポートしているが、このリソースでは受け付けないという意味です。実務上の差は次に向かう先です。プロキシや古いサーバーから返る `PATCH` の 501 は迂回すべき機能の欠落、自分のアプリケーションから返る `PATCH` の 405 は登録し忘れたルートです。どちらも §15.1 でヒューリスティックにキャッシュ可能なので、どちらも原因より長生きしえます。 |
| 404 | 一致しないメソッドに 404 を返すルーターは、答えの有用なほうの半分を隠しています。§15.5.6 がその区別を重要にしている理由がこれです。405 には `Allow` が付き、404 には何も付きません。判定はコマンド 1 本です。あるパスが POST には 404、GET には 200 を返すなら、それは番号を間違えて着ている 405 であって、見るべきはパスではなくメソッドです。 |
| 301 | いま見ている 405 は、呼び方ではなくリダイレクトの副産物かもしれません。§15.4.2 は、ユーザーエージェントが 301 を辿るとき歴史的に POST を GET へ書き換えてきたと記録していますし、実際には 302 でも同じことが起きます。だから `http://` から `https://` へ、あるいは末尾スラッシュを足すリダイレクトを跨いだ POST は、POST しか受け付けないルートに GET として到着しえます。メソッドを保つリダイレクトは 307 と 308 で、API がそちらを使うべき理由です。 |
CORS preflight | JSON のボディやカスタムヘッダーを持つオリジン跨ぎのリクエストの前には、ブラウザ自身が生成する `OPTIONS` が飛びます。OPTIONS のルートが無いサーバーはそれに 405 で答えます。するとブラウザはプリフライトが成功しなかったとして CORS の失敗を報告するので、メッセージは間違った問題を名指しします。本当の原因は、失敗したリクエストの 1 行上にあるネットワークパネルの 405 です。 |
Allow | §10.2.1 は `Allow` を、対象リソースがサポートするメソッドの一覧と定義し、§15.5.6 は 405 でそれを送ることを必須にしています。だから無いのはヒントではなくサーバーのバグです。値が空の場合は注意して読んでください。空の `Allow` は「このリソースはどのメソッドもサポートしない」という合法な言い方で、ヘッダーが存在しないのとは別の主張です。 |
どれもブラウザ内で完結します。アップロードは発生しません。
405 を調べているとき、ついでに読むことになりがちなコードです。
ページではなく応答ヘッダーを読んでください。curl -sS -o /dev/null -D - https://example.com/path が本文なしでヘッダーだけを出し、server・via・cf-ray の 3 つで、答えたのがどの層かが分かります。cf-ray の値があれば応答を扱ったのは Cloudflare で、その値はサポートが聞いてくる ID です。エッジを完全に外して確かめたいなら、--resolve example.com:443:203.0.113.10 を付けて同じリクエストを投げ直します。元のホスト名と SNI を送りつつ、指定したオリジンのアドレスへ接続するので、答えが変われば、エッジとオリジンの言い分が食い違っているということです。
ありません。分岐条件にしてはいけません。RFC 9112 はクライアントに理由句を無視するよう求めていますし、サーバーは自由に変えられますし、HTTP/2 と HTTP/3 にはそもそも理由句が存在しません。HTTP/1.1 では 404 Not Found として届くものが、HTTP/2 では素の 404 として届きます。このサイトが登録済みの理由句を載せているのは、それが検索される語であり HTTP/1.1 のログに出る文字列だからで、何かのソフトウェアがそれに依存しているからではありません。
5xx と 429 は再試行してよく、4xx はしないでください。次に投げても中身は同じだからです。応答に Retry-After が付いていればそれに従ってください。RFC 9110 がまさにこのために定義していて、値は秒数でも日時でも構いません。付いていなければ、ジッター付きの指数バックオフと明確な上限を使い、しかも冪等なメソッドに限ってください。再試行された POST はカードに二重で課金しかねません。すでに失敗しているサーバーへの再試行の嵐は、短い障害を長い障害に変える典型的な経路です。
変えられます。守るべき規則は 1 つだけ、そのコードが本当であること。応答を自動で読むもの——検索エンジン、監視、キャッシュ、クライアント側の再試行ロジック——はすべて数字だけで判断し、ページの中身は一切見ません。エラーページを 200 で返せば自分の監視から障害が隠れますし、無いページを 200 で返せばエラーが本文として索引されますし、単に不正なリクエストに 500 を返せば、当番の人をスタックの間違った半分へ送り込むことになります。
405 でまだ詰まっているなら、ステータスコード一覧をすべて見る。あるいは上に戻って、自分の立ち位置に向けて書かれたブロックを読んでください。