【実務・中級編】HTTP/1.1の条件付きGETリクエスト(If-Modified-Sinceと304 Not Modified) – HTTPプロトコル・通信規格実践ガイド

なぜ「無駄な通信」を繰り返すのか? HTTP/1.1 条件付きGETで帯域と心を救う技術

インフラエンジニアとして現場に立っていると、プロキシのログやWAFのトラフィック量を見て「このリクエスト、本当に必要か?」と自問自答する瞬間が必ず訪れます。特に、Web APIや静的コンテンツの配信において、クライアントが「前と同じデータだけど、一応もらっておくか」と全データを再取得する様子は、ネットワーク屋からすれば「帯域の浪費」以外の何物でもありません。

今回は、HTTP/1.1の古参にして最強の効率化ツール、条件付きGET(Conditional GET)について深掘りします。仕様書を暗記するのではなく、パケットがどうやり取りされ、どうキャッシュを活かすべきか、その本質を紐解いていきましょう。

—

304 Not Modified:通信を「省く」という贅沢

HTTPの通信コストは、単にデータ量だけの問題ではありません。TCPの3ウェイ・ハンドシェイクによるRTT(往復遅延)、そして何よりサーバー側の処理負荷(DBクエリや計算処理)が積み重なると、無視できない遅延となってユーザー体験を損ないます。

ここで登場するのが `If-Modified-Since` です。

仕組みのフロー

1. 初回リクエスト: クライアントがリソースを取得。サーバーは `Last-Modified` ヘッダーを付与して返す。
2. キャッシュ: クライアントはその日時を記憶しておく。
3. 2回目以降: クライアントは `If-Modified-Since` ヘッダーに「覚えている日時」を乗せて送信。
4. 判定: サーバー側は、リソースの最終更新日を比較。

  • 変更あり: `200 OK` で新しいデータを送る。
  • 変更なし: `304 Not Modified` を返す。

この304レスポンスにはボディが含まれません。ヘッダーだけを返し、クライアントに「君が持っているキャッシュ、まだ最新だよ」と伝えるだけで通信が完結する。これが、ネットワーク負荷を劇的に下げる鍵です。

—

実践:curlで挙動を追いかける

理論だけでなく、実際にパケットを覗いてみましょう。まずは `curl` を使って、どのようなヘッダーがやり取りされているか確認します。

1. 初回リクエスト( -I でヘッダーのみ表示)
curl -I https://example.com/api/data

出力例:
HTTP/1.1 200 OK
Last-Modified: Tue, 15 Oct 2023 10:00:00 GMT
…

次に、この `Last-Modified` を使って条件付きGETをシミュレートします。

2. 条件付きGET( -H でIf-Modified-Sinceを指定)
curl -I -H “If-Modified-Since: Tue, 15 Oct 2023 10:00:00 GMT” https://example.com/api/data

出力例:
HTTP/1.1 304 Not Modified
Date: Tue, 15 Oct 2023 11:00:00 GMT
(ボディデータは返ってこない)

この瞬間、ネットワーク上には最小限のヘッダー情報しか流れていません。これが、大規模なWebサービスを支える「地味だが強力な」最適化の正体です。

—

API開発者が気をつけるべき「罠」

バックエンドでAPIを実装する際、この仕組みを意図的に組み込むか否かで、インフラの寿命が数年変わります。

実装時のTips

  • Last-Modifiedの精度: サーバー側の時刻は必ずGMT(UTC)で扱うこと。ローカルタイムを混ぜると、比較ロジックがバグの温床になります。
  • ETagの併用: `If-Modified-Since` は1秒単位の精度ですが、より厳密に行うなら `ETag` (Entity Tag) を使った `If-None-Match` が推奨されます。ハッシュ値で比較するため、1秒間に複数回更新されるようなデータでも確実に変更検知が可能です。

Python (Requests) でのキャッシュ制御例

フロントエンドやスクレイピングで条件付きGETを活用する際は、以下のようにヘッダーを制御します。

import requests

クライアント側でのキャッシュ管理ロジック
last_modified_cache = “Tue, 15 Oct 2023 10:00:00 GMT”

headers = {
“If-Modified-Since”: last_modified_cache
}

response = requests.get(“https://example.com/api/data”, headers=headers)

if response.status_code == 304:
print(“キャッシュが有効です。ローカルのデータを使います。”)
elif response.status_code == 200:
print(“データが更新されました。保存し直します。”)
# 実際にはここでレスポンスヘッダーのLast-Modifiedを更新保存する

—

トラブルシューティングの視点

現場で「なぜかキャッシュされない」という相談を受けたとき、私はまず以下の項目をチェックします。

1. Proxyの介入: 途中のCDNやリバースプロキシが `If-Modified-Since` を除去していないか?(設定次第で、キャッシュ効率を上げるためにあえて無視させるケースもあります)
2. 時刻のズレ: サーバーとクライアントの時計がズレていて、比較ロジックが期待通り動いていない可能性。
3. 動的生成の罠: APIが「毎回新しい結果を返す」設定(例えば、リクエストごとに `Date` ヘッダーを埋め込むような処理)になっていないか。

ネットワークは嘘をつきません。`tcpdump` やブラウザのデベロッパーツールで流れるヘッダーを一つずつ確認すれば、答えは必ずそこにあります。

最後に:エンジニアとしての美学

HTTP/1.1という、四半世紀前の仕様が今なおWebの主役である理由は、この「無駄を省くための洗練された仕組み」にあると私は信じています。

新しい技術や複雑なフレームワークに目を奪われるのもいいですが、こうした「基本のプロトコル」を理解し、現場で使いこなせるエンジニアこそが、真の意味でインフラを強靭にできる人だと考えます。皆さんの設計するAPIが、今日も無駄なパケットを一つ減らしていることを願って。

コメント

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