読者です 読者をやめる 読者になる 読者になる

転職した

調べた事、学んだ事、思った事、考えた事を個人的にまとめているだけなので内容は保証しないYO!

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が返るか

 

準備

# yum install httpd php php-mbstring

 

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

 

f:id:yellowring:20140517214809p:plain

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>

f:id:yellowring:20140517215419p:plain

 指定通り、HOGEが入っていることが確認できます。

If-None-MatchをPHPなどで取得してEtagと比較、304 Not Modifiedを返せるか

f:id:yellowring:20140517215541p:plain

画像の通り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のレスポンスを貰った時のヘッダ情報は下記の通り。

f:id:yellowring:20140517215644p:plain

編集した結果、ファイルサイズと日付が変更されています。

# 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が返ってくるはずです。 

f:id:yellowring:20140517215744p:plain

予想通り、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を見たりしてレスポンスヘッダに設定いるという事だろう。

 

*1:そもそも、HTTPプロトコルの知識が不足してたりするんですが、まあそれはおいておくとして。