405 Method Not Allowed:その「沈黙」はAPI設計の美学とセキュリティを映し出す鏡だ
インフラエンジニアやバックエンドエンジニアとして生きていると、幾度となくHTTPステータスコードの洗礼を受ける。200 OKの美しさ、500番台の絶望、そして400番台が持つ「お前のリクエストに問題がある」という厳格なメッセージ。
中でも、今回スポットを当てる 405 Method Not Allowed は、非常にユニークなステータスコードだ。
「サーバーにそのリソース(URL)は存在する。しかし、お前が投げたそのHTTPメソッドは、そこでは許可されていない」という、絶妙な門前払い感を放っている。
単なる設定ミスとして片付けられがちだが、RFCの仕様を紐解き、Web APIの4つの原則(制約)の文脈で捉え直すと、この405というコードは「堅牢なAPI設計とセキュリティ担保の最前線」でいぶし銀の働きをしていることに気づくはずだ。
今回は、数々の修羅場をくぐり抜けてきたシニアインフラエンジニアの視点から、405が生まれるメカニズム、裏側のパケットの挙動、そして実務で役立つデバッグとハンドリングの極意を叩き込んでいこう。
—
1. RFCが定義する405の仕様と「Allowヘッダー」という絶対のルール
まずは原点であるRFC(HTTP/1.1の仕様であるRFC 7231、および最新のRFC 9110)を確認しておこう。
405 (Method Not Allowed) ステータスコードが示すのは、「リクエストラインで指定されたメソッドが、リクエストURIによって識別されるリソースに対して知られているが、そのリソースによってサポートされていない」という状態だ。
ここで非常に重要な、そして多くのジュニアエンジニアが実装を忘れがちなRFCの必須要件がある。
それは、405レスポンスを返す際、サーバーは必ず Allow レスポンスヘッダーを含めなければならない というルールだ。
HTTP/1.1 405 Method Not Allowed
Allow: GET, POST, OPTIONS
Content-Type: application/json
Content-Length: 42
{"error": "Method Not Allowed"}
この Allow ヘッダーには、そのリソースに対して 「現在、許可されている有効なHTTPメソッドのリスト」 をカンマ区切りで列挙する必要がある。クライアント側(あるいは自動化されたAPIクライアント)は、この Allow ヘッダーを見ることで、「あ、ここでは POST はダメで PUT なんだな」と自己修復や次のアクションを決定できる。これが「自己記述性(Self-descriptive messages)」というRESTの美学に直結しているのだ。
—
2. 405が発生する通信フロー(シーケンス)
では、実際にブラウザやAPIクライアントが POST を想定しているエンドポイントに対して、誤って DELETE メソッドを叩いてしまったときの通信とサーバー内部の挙動をシーケンスとして追ってみよう。
[Client] [Web/API Server (Nginx/App)]
| |
| --- [ 1. DELETE /api/v1/users/123 ] -------------> |
| |
| | (ルーティング・メソッド判定)
| | "DELETE" はこのルートに無い!
| |
| <--- [ 2. HTTP/1.1 405 Method Not Allowed ] ------ |
| Allow: GET, PUT, DELETE (※実際の設定による) |
| |
1. リクエストの到達: クライアントが DELETE メソッドで /api/v1/users/123 を叩く。
2. サーバーのルーティング評価: Webサーバー(NginxやApache)やバックエンドのルーター(Express, Django, Laravelなど)がリクエストを受け取る。
3. メソッド不一致の検知: パス(/api/v1/users/123)に合致するルートはあるが、そこに DELETE メソッドのハンドラーが定義されていない、あるいはHTTPメソッドの制限(許可リスト)に引っかかった。
4. 405の返送: サーバーは処理を拒絶し、適切な Allow ヘッダーを添えて 405 をクライアントへ送り返す。
ここで「そもそもパスが存在しない場合」との違いに注意してほしい。
パスそのものが存在しない場合は 404 Not Found になる。405が返ってくるということは、「リソースの住所は正しいが、やろうとした手段(動詞)が間違っている」という、極めて示唆に富んだ状態なのだ。
—
3. 実務で405に直面する主な原因とトラブルシューティング
現場で「なぜか405が出る」というトラブルシューティングを行う際、大抵の原因は以下の3つのどれかに集約される。
原因A:ルーティング設定のミス(バックエンドの仕掛け)
例えば、ユーザー情報の取得 (GET) と更新 (PUT) は同じURLに生やすのがRESTの美しい設計(美しいエンドポイントURLの設計)だ。しかし、コントローラーやルーティング定義の記述漏れにより、PUT の実装を忘れていると、GET が成功するのに PUT を投げた瞬間だけ 405 が返るという現象が起きる。
原因B:Webサーバー(Nginx等)の静的ファイル制限
APIサーバーの手前にリバースプロキシとしてNginxを挟んでいる構成でよくあるのが、POST リクエストを静的ファイル置き場(HTMLや画像が置いてあるディレクトリ)に向けてしまったケースだ。Nginxはデフォルトで静的ファイルに対する POST や PUT を許可していないため、405を返す。
原因C:CORS(Cross-Origin Resource Sharing)のプリフライトリクエストの誤解
モダンなWebフロントエンド(ReactやVue.jsなど)から別ドメインのAPIへリクエストを飛ばす際、ブラウザは本番リクエストの前に OPTIONS メソッドで事前確認(プリフライト)を行う。この OPTIONS メソッドに対するルーティングやサーバー側のプレフライト応答設定が抜けていると、OPTIONS が 405 を食らい、結果としてフロントエンド側でCORSエラーとして検知される。
—
4. 各種環境におけるコード・設定の実装例
ここからは、実務でこの405やメソッド制御をどのように扱い、コードとして落とし込むのかを具体的に見ていこう。
① FastAPI (Python) でのメソッド制御と明示的なハンドリング
PythonのモダンなフレームワークであるFastAPIでは、デコレータでメソッドが厳密に制限される。意図しないメソッドが来た場合の挙動を確認してみよう。
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
# このエンドポイントは GET のみ許可されている
@app.get("/api/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id, "name": "サンプルアイテム"}
# もしここにクライアントが POST や PUT を投げると、
# FastAPI/Starlette のルーティング層が自動的に 405 Method Not Allowed を返し、
# レスポンスヘッダーに "Allow: GET, HEAD" を自動付与してくれる。
② Nginxにおけるメソッド制限のカスタム設定
Nginxで特定のディレクトリやLocationに対して、許可するメソッドをホワイトリスト形式で厳格に絞り込む設定例だ。これによって、意図しない PUT や DELETE による不正なアタックや設定ミスを水際で防ぐ。
server {
listen 80;
server_name api.example.com;
location /api/v1/public/ {
# 許可するメソッドを限定する(これ以外はすべて 405 を返す)
limit_except GET HEAD OPTIONS {
deny all;
}
proxy_pass http://backend_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
*この設定では、/api/v1/public/ 配下においては GET, HEAD, OPTIONS 以外のメソッド(POST, PUT, DELETE など)を受け付けた瞬間、Nginxが自律的に 405 Method Not Allowed を返すようになる。*
③ クライアントサイド (JavaScript / Fetch API) でのデバッグ
フロントエンド側からAPIを叩く際、405に遭遇したときにどのようなコードでそれをキャッチし、ログに残すべきかの実例だ。
async function updateUserData(userId, userData) {
try {
const response = await fetch(`https://api.example.com/api/v1/users/${userId}`, {
method: 'PUT', // ここで誤って GET しか許可されていないエンドポイントを指定したとする
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <your_token>'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
if (response.status === 405) {
// Allow ヘッダーの値を取得してデバッグに活かす
const allowedMethods = response.headers.get('Allow');
console.error(`[405 Error] このエンドポイントではこのメソッドは使えません。許可されているメソッド: ${allowedMethods}`);
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('APIリクエストに失敗しました:', error);
}
}
④ curl を使ったCLIでの疎通確認(デバッグの基本)
インフラエンジニアの必須ツール curl を使って、実際にサーバーがどのメソッドを許可しているかを調べる -v(詳細出力)と -X オプションの組み合わせだ。
# 疑わしいエンドポイントに対してあえて怪しいメソッドや意図したメソッドを投げてみる
curl -i -X POST https://api.example.com/api/v1/items/456
実行結果として以下のようなレスポンスが返ってきたら、サーバー側で正しくメソッド制御が行われている(あるいは実装漏れがある)ことが一目でわかる。
HTTP/1.1 405 Method Not Allowed
Server: nginx/1.18.0
Date: Tue, 24 Oct 202X 12:00:00 GMT
Content-Type: application/json
Content-Length: 38
Allow: GET, PUT
{"detail": "Method Not Allowed"}
—
5. シニアエンジニアからの実務的Tips:405を味方につける設計思想
最後に、Web API設計の現場で405ステータスコードとどう向き合うべきか、実務的なTipsをいくつか授けたい。
1. 「なんとなく500を返す」愚を犯さない
フレームワークのルーティング例外を雑にキャッチしてすべて 500 Internal Server Error に落とし込んでいるコードベースを見かけることがあるが、これは最悪だ。クライアント側から見れば「サーバーがバグったのか、自分のリクエストの仕方が悪いのか」が判別つかない。メソッド違いであれば、厳格に 405 を返し、Allow ヘッダーを返す設計を徹底しよう。
2. セキュリティの多層防御としてのメソッド制限
「使わないメソッドはそもそも受け付けない」というポリシーは、アタックサーフェス(攻撃表面)を減らす上で極めて有効だ。不要な DELETE や PUT の穴をあけないために、APIゲートウェイやNginx、バックエンドフレームワークの各層でしっかりとメソッドのホワイトリスト運用を行うこと。
3. 美しいURL設計と組み合わせる
/users/update のような動詞を含んだURLではなく、/users/123 という綺麗なリソースベースのURL設計(REST原則)を守ってこそ、405というステータスコードはその真価を発揮する。同じURL(名詞)に対して、動詞(メソッド)を変えるだけで振る舞いが変わるというRESTの醍醐味を、405はエラーという形で下支えしているのだ。
パケットの往来に目を凝らし、RFCの仕様に裏打ちされた正確なエラーハンドリングを実装すること。それこそが、プロダクトの信頼性を高め、上流から下流まで見通せる真のインフラ・バックエンドエンジニアへの近道である。
コメント