静的サイトの更新とキャッシュ
配信元の現在版、キャッシュが保持する版、HTMLが参照するアセットをそれぞれ確認します。
説明
配信先のファイルを更新しても、利用者が保存済みの応答を使えば、すぐに新しい内容へ切り替わるとは限りません。HTMLを再検証し、変更したJavaScriptには別のURLを使う構成なら、HTMLとアセットの版を対応させやすくなります。
ローカルHTTPの配信元と、時刻を固定したクライアントキャッシュを使います。同じ/app.jsの上書きで旧版が残る場合と、app.v1.js/app.v2.jsを分ける場合を比較し、ETagによる304と配信元のロールバックも確認します。
配信元の現在版、キャッシュが保持する版、HTMLが参照するアセットをそれぞれ確認します。
1版1を取得する
Releasesは実HTTPサーバーを起動します。クライアント側の時計は0、JavaScriptの保存期限は60秒です。
from releases import ReleasesHTTP配信元と固定時計のキャッシュを読み込む
release = Releases()独立した配信環境を作る
release.fetch("/")HTMLを取得する
release.fetch("/app.js")共通URLのJavaScriptを取得するHTMLはapp.v1.jsを参照し、共通URL/app.jsの本文もbuild=v1です。初回なので両方HTTP 200で取得しています。
2同じURLでは保存済み応答を使う
60秒の期限内に同じJavaScriptを取得します。
release.fetch("/app.js")期限内のキャッシュを参照するcache HITでbuild=v1です。配信元への要求を省いて、保持する本文を使っています。
3配信元だけを版2へ更新する
配信元の現在版をv2へ変え、同じURLを取得します。
release.publish("v2")配信元を版2へ切り替える
release.fetch("/app.js")旧URLのキャッシュを読む配信元はv2でも、キャッシュからv1が返ります。配置完了と、利用者が新しい本文を取得した時点は一致しません。
4HTMLから新しいアセットURLへ進む
HTMLは毎回再検証します。更新されたHTMLが指すapp.v2.jsを取得すると、旧URLとは別のキャッシュ項目になります。
release.fetch("/")更新後のHTMLを再取得する
release.fetch("/app.v2.js")新しいアセットURLを取得するHTMLも新しいURLの本文もHTTP 200でv2になります。URLを版ごとに変えることで、旧URLの期限を待たず新しい内容を取得できます。
5HTMLが同じなら304になる
変更のないHTMLへIf-None-Match付きで要求します。サーバーのETagが一致すると本文を再送しません。
release.fetch("/")ETagを使って変更の有無を確認するHTTP 304で、保存していたHTMLの本文を使います。304自体に新しいHTML本文が入っているわけではありません。
6旧URLも期限後に再取得する
時計を60秒へ進め、/app.jsを取得します。
release.advance(60)保存期限まで時計を進める
release.fetch("/app.js")期限後の共通URLを再取得する期限切れ後は配信元へ要求し、HTTP 200でbuild=v2になります。新旧版が混在する期間はキャッシュの設定にも左右されます。
7HTMLを旧版へ戻す
配信元をv1へ戻します。版付きアセットを両方残しているため、旧HTMLの参照先も取得できます。
release.publish("v1")配信元のHTMLを旧版へ戻す
release.fetch("/")旧版HTMLを取得する
release.fetch("/app.v1.js")旧版の参照先を取得するHTMLとapp.v1.jsがv1にそろいます。ロールバック時にも、HTMLが参照する旧アセットを保持する必要があります。
8配信先と保存済み応答は別に管理する
固定のDNS設定例を読み、同じURLのキャッシュを再度確認します。DNSは名前の到達先を決めますが、保持済みの本文を書き換える操作ではありません。
print配信先名を示す固定DNS記録を読む({"name": "learn.example.test", "type": "CNAME", "target": "course.pages.dev", "ttl": 300})
release.fetch("/app.js")共通URLに残る版を確認するDNS設定例を読んだ後も/app.jsはキャッシュのv2です。配信元をv1へ戻すだけで全URLの保存済み応答が消えるわけではありません。
まとめ
配信元の切替だけでは期限内のキャッシュは変わりません。
HTMLと版付きアセットを対応させると、新しいURLで新しい内容を取得できます。
DNS、配信元の版、各URLのキャッシュは別々に確認します。
操作履歴と解説を振り返れます。
Python HTTP server / 固定時計のクライアントキャッシュ
from releases import Releases
release = Releases()
release.fetch("/")
release.fetch("/app.js")origin HTTP 200 / <script src="/app.v1.js"></script> origin HTTP 200 /app.js build=v1
release.fetch("/app.js")cache HIT /app.js build=v1
release.publish("v2")
release.fetch("/app.js")origin_release=v2 cache HIT /app.js build=v1
release.fetch("/")
release.fetch("/app.v2.js")origin HTTP 200 / <script src="/app.v2.js"></script> origin HTTP 200 /app.v2.js build=v2
release.fetch("/")origin HTTP 304 / <script src="/app.v2.js"></script>
release.advance(60)
release.fetch("/app.js")now=60 origin HTTP 200 /app.js build=v2
release.publish("v1")
release.fetch("/")
release.fetch("/app.v1.js")origin_release=v1 origin HTTP 200 / <script src="/app.v1.js"></script> origin HTTP 200 /app.v1.js build=v1
print({"name": "learn.example.test", "type": "CNAME", "target": "course.pages.dev", "ttl": 300})
release.fetch("/app.js"){'name': 'learn.example.test', 'type': 'CNAME', 'target': 'course.pages.dev', 'ttl': 300}
cache HIT /app.js build=v2演習完了