メインコンテンツまでスキップ

HTTP — メソッド・ステータス・ヘッダー

HTTP/1.1 はテキストベースの単純なプロトコルです。「何をしたいか」を 1 行目に書き、付随情報をヘッダーに並べ、本体を続ける。この構造さえ掴めば、API の設計もデバッグも見通しがよくなります。

この章で学ぶこと

  • リクエストとレスポンスの構造と、メソッドがどこに現れるか
  • 9 つの標準メソッドと、安全性・冪等性という 2 つの性質
  • PUT / PATCH / POST の選び分けと、状態を値で書けない要求の扱い
  • ステータスコードの 5 分類と、選び方
  • HTTP/1.1 から HTTP/3 までで何が変わったか
前提知識

TCP/IP の階層モデルを前提にします。HTTP はアプリケーション層のプロトコルです。

この章で扱わないこと

HTTP に隣接する話題は、それぞれの章とガイドが扱います。

観点参照先
通信路の暗号化と証明書の検証TLS と証明書
クッキーの属性とスコープクッキー
ブラウザのオリジン境界と CORSオリジンと CORS
再送の設計 (回数・間隔・冪等キー)リトライと冪等性
キャッシュの可否と有効期限の指定HTTP キャッシュと CDN
URL の設計とバージョニング、フレームワークでのルート定義Laravel API 開発ガイド — ルーティングと最初のエンドポイント
レスポンスの出力形式の設計Laravel API 開発ガイド — API リソース

メッセージの構造

HTTP のやり取りは、リクエストとレスポンスの 2 種類のメッセージでできています。

リクエスト

POST /api/orders HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer eyJhbGci...

{"itemId": 42, "quantity": 2}
部分内容
1 行目 (リクエストライン)メソッド + パス + プロトコルバージョン
2 行目以降 (ヘッダー)付随情報。空行まで続く
空行の後 (ボディ)送るデータ。メソッドによっては無い

メソッドはリクエストラインにだけ現れます。ヘッダーで指定するものでもボディに入れるものでもありません。

レスポンス

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/orders/1001

{"id": 1001, "status": "accepted"}
部分内容
1 行目 (ステータスライン)プロトコルバージョン + ステータスコード + 理由句
2 行目以降ヘッダー
空行の後ボディ

レスポンスにメソッドは含まれません。レスポンスが持つのはステータスコードです。「何をしたか」はリクエスト側の情報で、返す側は「どうなったか」を返します。

標準メソッドは 9 つ

メソッド何をするか安全冪等
GETリソースを取得する
HEADGET と同じだがボディを返さない
POSTデータを送信する。状態変更や副作用を伴う××
PUTリソースを送信内容で置き換える×
DELETEリソースを削除する×
PATCHリソースを部分的に変更する××
OPTIONS通信オプションを問い合わせる
CONNECTトンネルを確立する××
TRACE経路をループバック試験する

UPDATEREMOVE は、上の 9 つには含まれません。更新は PUTPATCH、削除は DELETE を使います。SQL の UPDATE / DELETE や CRUD の語彙から連想して混同されやすいので、対応を押さえておきます。

やりたいことSQLHTTP
作成INSERTPOST
取得SELECTGET
全体を更新UPDATEPUT
一部を更新UPDATEPATCH
削除DELETEDELETE

PUT と PATCH と POST を選び分ける

3 つはどれも状態を変えますが、送る内容の意味が違います。

メソッド送るものサーバーがすること
PUT完成後の全体送られた内容でそのまま置き換える
PATCH変更の指示送られた指示を今の内容に適用する
POST処理してほしい内容受け取って処理する。何をするかはサーバーが決める

PUTPATCH は、URL が 1 つの「もの」を指していて、その中身を送れるという形を前提にしています。/users/1 はユーザー 1 人を指すので、PUT /users/1 は「このユーザーをこの内容にする」、PATCH /users/1 は「このユーザーのここだけ変える」になります。この形が使えるのは、変えたい状態を値として書けるときです。名前を「田中」にする、メールを差し替える、公開フラグを立てる。どれも「こうなっていてほしい」を値で表せます。

送りたいものが状態でなく動作のこともあります。「残高から 500 円引く」を PUT で書くなら、クライアントが今の残高を読み、引いたあとの値を送ることになります。読んでから送るまでに他の更新が入れば、その分は上書きで消えます。「引く」という動作をそのまま送れば、読み出しと計算をサーバー側で一度に済ませられます。これを運ぶのが POST です。送る内容も、受けて何をするかも定められていないので、動作の依頼を表せます。

PATCH{"delta": -500} のような差分を入れる形も書けます。PATCH の RFC は差分の書式を定めておらず「実装が対応を求められる既定の書式は 1 つも無い」としているので、これ自体は仕様違反ではありません。ただし広く使われる JSON Patch の操作は追加・削除・置換・移動・複製・検査の 6 つで、加減算がありません。delta の読み方はその API 独自の取り決めになり、他の人が見て何が起きるか分かりません。

選ぶ順序はこうなります。前提は「更新したい『もの』の URL がある」ことで、そこが崩れると 3 に落ちます。

  1. その URL の中身を、なりたい状態で全部書けるなら PUT
  2. 同じ URL に、変えたい部分だけ書けるなら PATCH
  3. 「もの」がまだ無い (新規作成)、または状態でなく動作を頼みたいなら POST

新規作成が POST なのは 3 の前半によります。作る前は URL がまだ決まっていないので、/orders のような一覧の URL へ「これを作ってほしい」と送ります。どの URL になるかはサーバーが決めて Location ヘッダーで返します。

安全性と冪等性

この 2 つは似ていますが別の性質です。

安全 (safe) — サーバーの状態を変更しない。読み取り専用。

冪等 (idempotent) — 同じリクエストを何回送っても、結果の状態が 1 回送ったときと同じになる。

安全 ⊂ 冪等

安全なメソッドは必ず冪等です。何も変えないので、何回やっても状態は同じだからです。逆は成り立ちません。

DELETE が分かりやすい例です。状態を変えるので安全ではありませんが、冪等です。同じリソースを 2 回消しても、最終的な状態は「そのリソースが無い」で変わりません。2 回目が 404 を返したとしても、状態が冪等であることとレスポンスが同じであることは別の話です。

POST が冪等でないのは、送るたびに新しいリソースが増えるからです。注文 API を 2 回叩けば注文が 2 件できます。これが「送信ボタンの二度押し」が問題になる理由で、対策として冪等キー (同じキーのリクエストは 1 回しか処理しない) を導入することがあります。

PUTPATCH の差もここに出ます。PUT は「この内容で置き換える」なので何回やっても同じ状態になり冪等です。PATCH は「この指示を適用する」なので、指示の内容によっては冪等になりません (前の節で見た「1 減らす」型の差分)。

この性質が効くのは再送の判断です。通信が途中で切れて応答が分からないとき、冪等なメソッドなら安全に再送できます。冪等でないなら、再送すると二重に処理される危険があります。再送を何回・どの間隔で行うかはリトライと冪等性で扱います。

ステータスコード

先頭の数字で 5 つに分かれます。

分類意味代表例
1xx情報101 Switching Protocols
2xx成功200 OK / 201 Created / 204 No Content
3xxリダイレクト301 Moved Permanently / 302 Found / 304 Not Modified
4xxクライアント側の誤り400 / 401 / 403 / 404 / 409 / 422 / 429
5xxサーバー側の誤り500 / 502 / 503

4xx5xx の境目が実務では重要です。リクエストの内容に問題があるなら 4xx、サーバーが原因なら 5xx です。ここを取り違えると、監視のアラートが的外れになります。バリデーションエラーを 500 で返していると、正常な入力ミスがサーバー障害として通知されます。

紛らわしい組み合わせを整理します。

使う場面
401 Unauthorized認証が済んでいない。誰か分からない
403 Forbidden認証は済んでいるが認可が下りない。誰かは分かるが権限が無い
404 Not Found存在しない。存在を隠したい場合に 403 の代わりに使うこともある
400 Bad Requestリクエストの形式が不正
422 Unprocessable Content形式は正しいが内容が業務ルールに反する
409 Conflict現在の状態と競合する (重複登録、版数の食い違い)
301 / 302別の場所へ移った。POST への応答としてこれを受けると、追随のときにメソッドを GET へ変える実装がある
307 / 308同じくリダイレクトだが、メソッドとボディを変えずに追随させたいときに使う

ヘッダー

ヘッダーは「本文以外の付随情報」を運びます。よく使うものを役割で分けます。

役割
中身の説明Content-Type / Content-Length / Content-Encoding
認証Authorization / WWW-Authenticate
キャッシュCache-Control / ETag / Last-Modified
交渉Accept / Accept-Language / Accept-Encoding
状態の保持Cookie / Set-Cookie
オリジン間の制御Origin / Access-Control-Allow-Origin / Access-Control-Allow-Headers
セキュリティContent-Security-Policy / Strict-Transport-Security

Content-Type は本文の形式に加えて、どの文字コードで読むかも運びます。送り手と受け手でここがずれると文字化けします (文字コード)。

HTTP はステートレスなプロトコルです。サーバーはリクエスト間で状態を覚えません。ログイン状態を保つには、クッキーやトークンを毎回のリクエストに載せて、サーバー側で照合します。「状態を持たない」のが原則で、状態が要るなら明示的に運ぶ、という設計です。

バージョンによる違い

登場トランスポート主な変更
HTTP/1.11997TCP接続の再利用 (keep-alive)。1 接続で 1 リクエストずつ処理する
HTTP/22015TCP多重化。1 接続で複数のやり取りを並行させる。ヘッダー圧縮。ここからテキストでなくバイナリのフレームで運ぶ
HTTP/32022UDP (QUIC)TCP をやめて接続確立を短縮。パケット欠落の影響を局所化する

接続確立が短いのは、QUIC が TLS 1.3 のハンドシェイクを自分の接続確立に組み込んでいるためです。TCP の確立とセキュアな接続の確立が別々の往復にならず、1 往復で済みます。

HTTP/1.1 では、1 つの接続で前のレスポンスが返るまで次を送れないという制約がありました (ヘッドオブラインブロッキング)。これを避けるためにブラウザは同一ホストへ複数の接続を張っていました。

HTTP/2 は 1 つの接続に複数のストリームを流せるようにしてこれを解消しましたが、TCP の層では依然として 1 つのパケットが落ちると後続がすべて待たされます。HTTP/3 が UDP へ移ったのは、この TCP 由来の詰まりを避けるためです。

メソッドやステータスコードの意味はバージョンをまたいで変わりません。変わったのは運び方だけです。

よくある誤解

GET でも、ついでに状態を変えてかまわない」GET は安全なメソッドとして定義されています。「一覧を取得したついでに既読フラグを立てる」ような設計にすると、クローラやプリフェッチが勝手に状態を変えてしまいます。読み取りに副作用を持たせるなら POST を使います。

POST は新規作成専用」 — 作成に使われることが多いだけで、そう定められてはいません。冪等でなくてよく、URL が指す「もの」の中身としては書けない操作も表せる汎用のメソッドです。「在庫を 1 つ減らす」を POST にするのはこの性質によります。

DELETE は冪等ではない」 — 冪等です。2 回目が 404 を返しても、結果の状態は変わりません。冪等性は状態についての性質で、レスポンスが同じかどうかではありません。

「安全なら冪等、冪等なら安全」 — 安全なら必ず冪等ですが、逆は成り立ちません。PUTDELETE は冪等ですが安全ではありません。

確認問題

問 1. 社内の API 設計レビューで「更新は UPDATE、削除は REMOVE メソッドを使う」という案が出ました。どう指摘しますか。

解答と解説

答え: どちらも標準の 9 つには入りません。更新は PUTPATCH、削除は DELETE を使います。

標準メソッドは次の 9 つです。

GET / HEAD / POST / PUT / DELETE / PATCH / OPTIONS / CONNECT / TRACE

UPDATEREMOVE が混同されやすいのは、SQL や CRUD の語彙に引きずられるためです。対応を整理しておきます。

やりたいことSQLHTTP
全体を置き換えるUPDATEPUT
一部だけ変えるUPDATEPATCH
削除するDELETEDELETE

独自のメソッド名を使うと、プロキシやキャッシュや API クライアントが解釈できません。HTTP のメソッドは共通語彙なので、勝手に増やすと途中の中継機が扱えなくなります。

「メソッドで表現できない操作をしたい」という要求なら、POST を使ってパスや本文で操作を表すのが現実的な着地点です。

問 2. 「在庫を 1 つ減らす」API を作ります。POST・PUT・PATCH のどれが適切ですか。

解答と解説

答え: POST

冪等性で判断します。「1 つ減らす」は送るたびに結果が変わるので冪等ではありません。冪等でない操作に冪等なメソッド (PUT / DELETE) を割り当てると、クライアントやプロキシが「再送しても安全」と判断して二重に減らす危険があります。

PATCH も冪等ではないので候補になりますが、広く使われる JSON Patch に加減算の操作がありません (追加・削除・置換・移動・複製・検査の 6 つ)。差分の読み方を独自に決めることになるので、動作の依頼として POST を使うほうが伝わります。

冪等にしたいなら、設計を変えて結果の値を送る形にします。

PUT /api/items/42/stock HTTP/1.1
Content-Type: application/json

{"quantity": 9}

これなら何回送っても在庫は 9 になり冪等です。ただし他のクライアントとの同時更新で上書きが起きるので、版数を使った衝突検出が別途要ります。

問 3. ログインしていないユーザーが管理画面の API を叩きました。返すべきステータスコードは何ですか。ログイン済みだが権限が無い場合はどうですか。

解答と解説

答え: 未ログインなら 401 Unauthorized、権限不足なら 403 Forbidden

区別の軸は認証か認可かです。

状況コード意味
トークンが無い、期限切れ、不正401誰か分からない。認証をやり直してほしい
認証済みだが権限が足りない403誰かは分かるが許可できない。やり直しても結果は変わらない

401 は「認証すれば通るかもしれない」、403 は「認証し直しても通らない」という違いです。クライアント側の挙動も変わり、401 ならログイン画面へ誘導し、403 ならエラーを表示します。この区別の背景は認証と認可で扱います。

なお、リソースの存在自体を隠したい場合に 403 の代わりに 404 を返す設計もあります。「権限が無い」と返すことで、そのリソースが存在する事実を漏らしてしまうためです。

まとめ

  • HTTP メッセージはリクエストライン (またはステータスライン) + ヘッダー + ボディの構造です
  • メソッドはリクエストラインにだけ現れます。ヘッダーやボディには置かれず、レスポンスにも含まれません
  • 標準メソッドは 9 つ。UPDATEREMOVE は含まれません
  • PUT は完成後の全体、PATCH は変えたい部分、POST は処理してほしい内容を送ります。なりたい状態を値で書けないなら POST です
  • 安全は状態を変えないこと、冪等は何回送っても結果の状態が同じこと。安全なら冪等ですが逆は成り立ちません
  • DELETE は安全ではありませんが冪等です
  • ステータスは 4xx がクライアント側、5xx がサーバー側の誤りです。401 は認証、403 は認可の問題です
  • HTTP はステートレスです。状態はクッキーやトークンで明示的に運びます
  • バージョンで変わったのは運び方だけで、メソッドやステータスの意味は同じです
関連リファレンス

次に読む

  • 認証と認可 — ここから第 6 部。401403 を分ける考え方を掘り下げます
  • クッキー — ステートレスなプロトコルで状態を運ぶ仕組み
  • TLS と証明書 — この通信路を暗号化している層