【実務・中級編】HTTP OPTIONSメソッドの仕様とCORSプリフライトリクエスト – HTTPプロトコル・通信規格実践ガイド

境界線を越えるための儀式:HTTP OPTIONSとCORSプリフライトの正体

Web APIの設計やインフラの運用を長くやっていると、避けては通れない「不可解な通信」に遭遇することがある。Chromeのデベロッパーツールを開くと、自分が送ったはずのPOSTリクエストの前に、なぜか見慣れない`OPTIONS`メソッドが飛び交っている……そんな光景だ。

多くの駆け出しエンジニアはここで躓く。「なぜ一度で済むはずの通信が二度手間になっているのか?」「このOPTIONSという謎の通信は、サーバーに何をもたらしているのか?」

今日は、RFC 7231に端を発し、現代のWebセキュリティの要であるCORS(Cross-Origin Resource Sharing)プリフライトリクエストについて、現場の視点から深く掘り下げていこう。

—

1. OPTIONSメソッドの本来の顔:サーバーへの「問いかけ」

HTTP/1.1の仕様において、`OPTIONS`メソッドは極めて紳士的な存在だ。これは「サーバーがどのメソッドを許可しているか」を事前に確認するためのリクエストである。

例えば、クライアントが「このエンドポイントに対してPOSTやDELETEは投げられるか?」と問うと、サーバーは`Allow`ヘッダーで「GETとPOSTなら許可するよ」と返す。

curlによる確認例:

サーバーがどのメソッドをサポートしているかを確認する
curl -v -X OPTIONS https://api.example.com/v1/resource

このリクエストに対して、サーバーは以下のようなレスポンスを返すのが標準的だ。

HTTP/1.1 200 OK
Allow: GET, POST, OPTIONS
Content-Length: 0

本来の用途はこれだけだ。しかし、現代のブラウザ環境では、このOPTIONSが「通信の安全性を担保するための厳格な門番」という役割を担うことになる。

—

2. プリフライトリクエスト:CORSの門番

Webブラウザは「同一生成元ポリシー(Same-Origin Policy)」という鉄の掟を持っている。異なるドメインへのリクエストは原則禁止だが、これを解除するためにCORSが使われる。

ここで問題になるのが、JavaScriptから送信する「複雑なリクエスト」だ。`Content-Type: application/json`を設定したり、`Authorization`ヘッダーを付与したりすると、ブラウザは「この通信はサーバーに悪影響を及ぼす可能性がある」と判断し、本番のリクエストを投げる前に「お伺い」を立てる。これがプリフライトリクエストだ。

通信シーケンスのリアル

1. プリフライト(OPTIONS): 「これからこのドメインから、このヘッダー付きでリクエストを送るけど許可してくれる?」
2. サーバーの応答: 「OK、そのOriginとヘッダーなら許可するよ(Access-Control-Allow-Origin 等を付与)」
3. 本番リクエスト(POST/PUT等): 許可が出たので、ようやく本来のデータを送る。

この二段構えこそが、現代のWebアプリケーションが安全にAPIを呼び出すための「儀式」なのだ。

—

3. 実践:CORSプリフライトをハンドリングする

インフラサイドやバックエンドの設計において、このプリフライトを適切に処理しないと、APIは「403 Forbidden」や「405 Method Not Allowed」で門前払いされてしまう。

nginxでの設定例(インフラ側での対応)

もしバックエンドに到達する前にプリフライトを捌くなら、nginxで以下のように記述するのが定石だ。

location /api/ {
# プリフライトリクエスト(OPTIONS)を即座に返す設定
if ($request_method = ‘OPTIONS’) {
add_header ‘Access-Control-Allow-Origin’ ‘https://your-frontend.com’;
add_header ‘Access-Control-Allow-Methods’ ‘GET, POST, OPTIONS’;
add_header ‘Access-Control-Allow-Headers’ ‘Authorization, Content-Type’;
add_header ‘Access-Control-Max-Age’ 86400; # 24時間は再送を不要にする(重要!)
return 204; # 本文なしで成功を返す
}
# 本番リクエストの処理へ…
}

Python (FastAPI) での対応

現代のモダンなフレームワークなら、標準機能でCORSミドルウェアを組むのがベストプラクティスだ。

from fastapi.middleware.cors import CORSMiddleware

アプリケーション全体でCORSを許可する例
app.add_middleware(
CORSMiddleware,
allow_origins=[“https://your-frontend.com”],
allow_methods=[“GET”, “POST”],
allow_headers=[“Authorization”, “Content-Type”],
)

—

4. 現場でハマるポイント:デバッグの極意

最後に、トラブルシューティングの現場でよくあるミスを共有しておこう。

  • `Access-Control-Max-Age`の欠如: これを忘れると、全てのAPIリクエストのたびにプリフライトが飛ぶ。通信遅延の大きな原因になるため、適切なキャッシュ時間を設定すること。
  • 認証トークンの扱い: `Access-Control-Allow-Credentials: true`を有効にする場合、`Access-Control-Allow-Origin`にワイルドカード()は使えない。必ず具体的なドメインを指定する必要がある。
  • プロキシの介入: CDNやロードバランサーがOPTIONSリクエストを「不審な通信」として弾いてしまうケースがある。ネットワーク構成図を見直す際、必ずOPTIONSメソッドが透過されているかを確認すること。

ネットワークを流れるパケットは嘘をつかない。ブラウザのコンソールでCORSエラーが出たとき、まずは「ブラウザが何を聞き、サーバーが何と答えたのか(あるいは無視したのか)」をネットワークタブで丹念に追うこと。それが、真のトラブルシューターへの第一歩だ。

技術は常に進化するが、HTTPというプロトコルの根底にある「対話のルール」は変わらない。このOPTIONSメソッドの挙動をマスターすれば、もうAPIの接続トラブルで夜を明かすことはなくなるはずだ。

コメント

タイトルとURLをコピーしました