ETagとは
HTTPレスポンスヘッダのひとつで、リソースの更新状況を示す識別子です。
主にブラウザのキャッシュに利用されます。
ETagを利用したキャッシュ動作の概要
クライアント(主にWebブラウザ)が対象URLのキャッシュを持っていない場合
- クライアントからサーバへリソース要求
- サーバはリソースの更新状況に応じたETagをレスポンスヘッダに付加してリソースを送信
- クライアントは受け取ったリソースを表示し、ETagが紐付けられたキャッシュを保存
クライアントがキャッシュを持っている場合
- クライアントはキャッシュに紐付くETagをIf-None-Matchとしてリクエストヘッダに付加した上で、サーバへリソース要求
- サーバは受け取ったIf-None-Matchの内容と該当リソースのETagを比較し、一致していなければサーバ側の持つETagをレスポンスヘッダに付加してリソースを送信、一致する場合はリソースは送信せず、HTTPステータスコード304のNot Modifiedを返す
- クライアントは304 Not Modifiedを受け取った場合はキャッシュの内容を表示し、リソースであればそれを表示した上で該当するキャッシュも更新する
ブラウザキャッシュに関連するHTTPヘッダはETagの他にもいくつかありますが、ETagを利用したキャッシュは有効期限を指定するタイプのキャッシュと違い必要最小限の通信は毎回行なう反面、サーバ側でコンテンツが更新されたにもかかわらず古いキャッシュがいつまでも表示されてしまうといったトラブルも回避しやすくなります。
WordPressをETag出力に対応させる
wp-blog-header.php
に対して以下の追記を行ないます。
// Set up the WordPress query.
wp();
+ // 出力バッファリングを有効に
+ ob_start();
// Load the theme template.
require_once ABSPATH . WPINC . '/template-loader.php';
+ // 出力バッファを取得し、バッファを削除
+ $obBuffer = ob_get_clean();
+
+ // ETagを生成
+ $eTag = md5($obBuffer);
+
+ // クライアントからのIf-None-Matchを取得
+ $ifNoneMatch = filter_input(INPUT_SERVER, 'HTTP_IF_NONE_MATCH');
+ // 生成したETagをIf-None-Matchと比較
+ // 圧縮転送が有効な場合サーバ側でETagに更にプレフィクスが付加され
+ // クライアントから受け取ったタグにもそれが付加されているため
+ // 完全一致ではなく、ここで生成したETagがIf-None-Matchに含まれているかで判別
+ if(strpos($ifNoneMatch, $eTag) !== false) {
+ // 一致していれば 304 Not Modified を返して終了
+ header('HTTP/1.1 304 Not Modified');
+ exit;
+ } else {
+ // クライアントからIf-None-Matchを受け取っていない、
+ // あるいはETagと異なる場合はレスポンスヘッダにETagを含めてページデータを返す
+ header(sprintf('ETag: "%s"', $eTag));
+ echo $obBuffer;
+ }
}
動作としては、送信されるべきデータを出力バッファを介して横取りし、そのデータを元にETagを生成、クライアントからのIf-None-Matchの状況に応じて、通常通りデータ(とETag)を送るか、未更新を意味する304 Not Modifiedのみを送るかを振り分けています。
出力バッファを横取りするのにちょうどよい位置のアクションフックが見つからなかったので、プラグインではなくwp-blog-header.php
を直接書き換える方法をとっています。
WordPressの自動更新で度々上書きされるファイルでもあるので、できる限り少ない追記で対応できるようにしたつもりです。
もし他に出力バッファを扱うようなプラグイン等を利用していた場合はお互い影響してしまい正常に動作しなくなることも考えられますので、その場合はこのETag出力対応の使用は中止して下さい。
ETagが出力されているか確認
大抵のWebブラウザにはデベロッパーツール(F12キーで起動できる場合が多い)がありますので、その中のネットワーク監視機能からそれぞれのデータのヘッダも確認することができます。
サーバから送られてくるレスポンスヘッダ内にETagのフィールドがあれば送信されてきています。
curl
コマンドの-I
オプションでもレスポンスヘッダを確認できます。
WordPressはHEADリクエストされた場合は上記で変更を加えた部分に到達する前に処理が終了するようなので、-X GET
オプションも付けてみてください。
一例
$ curl -I -X GET https://********.jp
HTTP/2 200
server: nginx
date: Thu, 18 Jun 2020 17:37:16 GMT
content-type: text/html; charset=UTF-8
x-powered-by: PHP/7.3.18
etag: "b3d0cb21d6686756a2f24a9311b2c467"
vary: Accept-Encoding
同じURLでありながらリクエストするたびETagが変化するようであれば、そのページには毎回変化する何かが含まれているということになります。
そのようなページであっても同じETagを出してしまってはキャッシュとして不適切ですので、それは正常な動作です。
ETagが付加されていることが確認できたら、そのETagをIf-None-Matchで指定して再度リクエストしてみます。
$ curl -I -X GET -H "If-None-Match: "b3d0cb21d6686756a2f24a9311b2c467"" https://********.jp
HTTP/2 304
server: nginx
date: Thu, 18 Jun 2020 17:41:00 GMT
HTTPステータスコード304が返ってくれば、正常に動作しています。
WordPressをETag出力に対応させたところで、どの程度恩恵があるのか
固定ページなど、長期間にわたって内容が変化しないようなページが多ければその分ネットワークトラフィックが減りますので、それなりに恩恵はあるかと思います。
表示内容に応じたETagを生成する都合上、送信する・しないに関わらずリソース(ページデータ)自体は生成しますので、サーバ側の負荷という点で見ればリソース分のトラフィックが節約できる以外はほとんど変わりはありませんが、ネットワークトラフィックの節約はとくに転送データ量に制限のあるモバイル通信などでは有用なのではないかと思います。
但し、ページ内容自体は変わっていないように見えても、そのページ内に例えばランダムに変わるおすすめページのピックアップや、コメントフォームにリロードのたびに内容の変わる認証コード系の隠しフィールドなどがあったりしてHTMLソースレベルでの変化があればETagも異なるものとなるため、その場合はキャッシュとしての動作は望めません。
ETagを使うことで恩恵のあるページがどの程度あるかを見極めながら利用を検討してください。