ETagについて調べてみた
High Performance Rails (long edition) // Speaker Deck
この資料でEtagって出てきてなんだこれと思ったので調べた話。*1
とはいえ、HTTP ETag - WikipediaにEtagとはなんぞやというところは十分書かれているので、実装上どうなっているかとか実際の挙動を見てみようという記事です。
とりあえず、知りたいこと
- O'Reilly Japan - ハイパフォーマンスWebサイトによるとApacheではEtagは"inode番号-ファイルサイズ-変更日時"のフォーマットらしいので本当なのか
- PHPのheader関数などを利用してEtagに独自の物を付けられるか
- If-None-MatchをPHPなどで取得してEtagと比較、304 Not Modifiedを返せるか
- Apacheで変更日時を変えずにファイルサイズを変更した場合、Last-Modifiedは変わらず、Etagのみ変わるか確認
- Etagのみ変わる場合、200 OKが返るか、304 Not Modifiedが返るか
準備
ApacheのEtagについて確認
下記のようなファイルを用意
# ls -ltri --time-style=full-iso
合計 8
261202 -rw-r--r--. 1 root root 830 2014-05-17 20:15:24.561532871 +0900 profile.gif
264093 -rw-r--r--. 1 root root 128 2014-05-17 20:22:55.643529003 +0900 index.html
index.htmlについては
Etag:"4079d-80-4f996c06d5389"
Last-Modified: Sat, 17 May 2014 11:22:55 GMT
profile.gifについては
Etag:"3fc52-33e-4f996a58a5c7c"
Last-Modified: Sat, 17 May 2014 11:15:24 GMT
となっている。なお、Apacheのバージョンは画像の通り2.2.15。現在、epelなどのリポジトリを追加せずにCentOSで入れられる最新のもの。
Etagについては16進数表記になっているので、下記の物を各々16進数に変換してみる。
261202 830 2014-05-17 20:15:24.561532871 +0900 profile.gif
264093 128 2014-05-17 20:22:55.643529003 +0900 index.html
$ bc
ibase=10;obase=16;
#profile.gif
#inode
261202
3FC52
#size
830
33E
#index.html
264093
4079D
#size
128
80
まとめると、下記の通りなので、確かにinode番号とファイルサイズを利用していることはわかります。
profile.gif: 3FC52-33E-
index.html: 4079D-80-
あとは、変更日時ですが、おそらくUNIX時間を16進数に変換していると思われるので、UNIX時間に変換します。
簡単に変換する方法ないかと調べてみたら、どうやら、date +%s でunix時間が取得できるようです(http://pooh.gr.jp/?p=82)。
ただ、MacOSではできなかったので、GNU dateの拡張かもしれませんね。
# date +%s --date "2014-05-17 11:15:24" #profile.gif
1400292924
# date +%s --date "2014-05-17 20:22:55" #index.html
1400325775
小数点以下と組み合わせる。
1400292924561532871
1400325775643529003
$ bc
ibase=10;obase=16;
1400292924561532871
136ED6FEEDFBA7C7
1400325775643529003
136EF4DFAB0E4F2B
比較してみると・・・あれ、合わない・・・。Etagの値を16進数変換してみると
$ bc
obase=10;ibase=16;
4F996C06D5389
1400325775643529
4F996A58A5C7C
1400325324561532
下3桁がないことが確認できた。下3桁というよりマイクロ秒までしか入れていないということか。
というわけで、きちんと"inode番号-ファイルサイズ-変更日時"という値のEtagが返ってきていることが確認できました(当たり前か)。
PHPのheader関数などを利用してEtagに独自の物を付けられるか
コレについては、HTTPの教科書に
Etag値の文字列にはルールは特に決まっておらず、サーバーによってさまざまなEtag値を割り当てます。(HTTPの教科書145p)
と記載されていることは確認しているので実際に試してみるという試みになります。
下記のようなスクリプトを用意してEtagの値を見てみます。
<?php
if(array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER) && $_SERVER['HTTP_IF_NONE_MATCH'] === 'HOGE') {
header('HTTP/1.1 304 Not Modified');
exit;
}
header ('Etag: HOGE');
?>
<!DOCTYPE HTML>
<html>
<head>
<title> Etag Test </title>
</head>
<body>
<?php print(date('c')); ?>
</body>
</html>
指定通り、HOGEが入っていることが確認できます。
If-None-MatchをPHPなどで取得してEtagと比較、304 Not Modifiedを返せるか
画像の通り304 Not Modifiedが返せています。
Apacheで変更日時を変えずにファイルサイズを変更した場合、Last-Modifiedは変わらず、Etagのみ変わるか確認
index.htmlに追記してファイルサイズを変えた上で、変更日時を元に戻してみます。
現在は下記の通り。
# ls -iltr --time-style=full-iso index.html
264093 -rw-r--r--. 1 root root 128 2014-05-17 21:30:09.268522512 +0900 index.html
この状態でキャッシュをクリアして 200 OKのレスポンスを貰った時のヘッダ情報は下記の通り。
編集した結果、ファイルサイズと日付が変更されています。
# echo >> index.html
# ls -iltr --time-style=full-iso index.html
264093 -rw-r--r--. 1 root root 129 2014-05-17 21:36:20.494527353 +0900 index.html
※なお、vimで編集した場合、inode番号が変わってしまうので、リダイレクトでファイルサイズを変更しています。vimが一時ファイル作ったりしてるせいかな?
これをtouchコマンドで元に戻します。
# touch -d "2014-05-17 21:30:09.268522512 +0900" index.html
# ls -iltr --time-style=full-iso index.html
264093 -rw-r--r--. 1 root root 129 2014-05-17 21:30:09.268522512 +0900 index.html
この状態でアクセスすれば、
Last-Modified: Sat, 17 May 2014 12:30:09 GMT
Etag: "4079d-81-4f997b0d98f2a"
の状態で200 OKが返ってくるはずです。
予想通り、200 OKが帰り、Etagのファイルサイズのみ変更されて返ってきました。
まとめ
- やはり、Apacheでは"inode番号-ファイルサイズ-UNIX時間"のフォーマットでEtagを返していた。
- ただし、UNIX時間については、マイクロ秒までを16進数に変換した物を返していた。
- Etagは自由な値にすることが可能。
- 独自にIf-None-Matchをみて、304 Not Modifiedを返すように実装することも可能。
- 冒頭のSpeaker Deckの資料でfresh_whenメソッドについて言及されているが、fresh_whenは内部でEtagを生成したり、If-None-Matchを見たりしてレスポンスヘッダに設定いるという事だろう。