レスポンスヘッダーのExpires: -1って何?

http://www.amazon.co.jp のレスポンスヘッダーを眺めていると Expires: -1 という不思議な Expire Date が設定されていた。
意味を調べてみた。

Expires レスポンスヘッダー

Expires はキャッシュの期限切れ日時を指定

Expires ヘッダーはコンテンツキャッシュの期限をコントロールし、 HTTP 1.0 から存在している。

RFC 2616 “Hypertext Transfer Protocol — HTTP/1.1” では次のように書かれている

The Expires entity-header field gives the date/time after which the response is considered stale.
http://tools.ietf.org/html/rfc2616#section-14.21

expiration time は HTTP-Date 形式(rfc1123-date | rfc850-date | asctime-date)でかく。

例) Expires: Thu, 01 Dec 1994 16:00:00 GMT

不正な日時

問題は、なんで日時のフィールドに “-1” なんていう数字が設定されているのか?

HTTP/1.1 clients and caches MUST treat other invalid date formats, especially including the value “0”, as in the past (i.e., “already expired”).
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

ということで、Expires に HTTP-Date 以外の形式が設定されると、”already expired” とみなされてキャッシュされない。
「過去」を動的に計算するのはかったるいので、シンプルに「-1」決め打ちにしているのだろう。

なお、未来の日付については “HTTP/1.1 servers SHOULD NOT send Expires dates more than one year in the future.” というように制限されているが、過去の日付については、そのような記述がないからなのか、多様な過去の日付が使われている。

Expires と Cache-Control の違い

HTTP/1.1 ではキャッシュをより細かく制御するために Cache-Control ヘッダーが追加され、max-age でキャッシュ期間を設定できる。
例えば Cache-Control: max-age=300 とすると、クライアントに 300秒はキャッシュするように促す。
max-age と expires の両方が設定されていた場合 max-age を優先する。

If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

IETF Httpbis ワーキンググループの一人で、”Caching Tutorial” などでも有名な Mark Nottingham は

  • ライブラリーを使って expires と Cache-Control:max-age の両方を設定する(Apache の mod_expires、 nginx の ngx_http_headers_module など)
  • Cache-Control: max-age だけを設定する

のいずれかを推奨している

実際の例

ここからは、実際の Web サーバがどのような Expires/Cache-Control を返しているか確認。

# amazon.co.jp
$ HEAD http://www.amazon.co.jp
200 OK
Cache-Control: no-cache
Connection: Keep-Alive
Date: Sat, 01 Feb 2014 04:32:47 GMT
Pragma: no-cache
Server: Server
Vary: Accept-Encoding,User-Agent
Content-Type: text/html; charset=Shift_JIS
Expires: -1
Client-Date: Sat, 01 Feb 2014 04:38:36 GMT
Client-Peer: 54.240.250.0:80
Client-Response-Num: 1
Keep-Alive: timeout=2, max=6
Set-Cookie: skin=noskin; path=/; domain=.amazon.co.jp
X-Amz-Id-1: 1G198XTD4DPPTNQP21KE
X-Amz-Id-2: cHJjNQk6G7uTB7JbLr83QfJ9XTssZwUAnkzLTWxuuGYuiE3MdXXS1SuHl9dVeC85

Cache-Content: no-cache
Expires: -1

# reddit
$ HEAD  http://www.reddit.com/r/gaming/comments/1wog7g/wtf_fifa/
200 OK
Cache-Control: private, must-revalidate, max-age=0
Connection: close
Date: Sat, 01 Feb 2014 04:32:30 GMT
Server: '; DROP TABLE servertypes; --
Vary: accept-encoding
Content-Type: text/html; charset=UTF-8
Last-Modified: Sat, 01 Feb 2014 04:30:56 GMT
Client-Date: Sat, 01 Feb 2014 04:38:19 GMT
Client-Peer: 125.56.218.96:80
Client-Response-Num: 1
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block

Cache-Control: max-age=0
Expires: blank

$ HEAD 'http://www.youtube.com/watch?v=uE7qVAtNwQk'
200 OK
Cache-Control: no-cache
Connection: close
Date: Sat, 01 Feb 2014 04:32:10 GMT
Server: gwiseguy/2.0
Content-Type: text/html; charset=utf-8
Expires: Tue, 27 Apr 1971 19:44:06 EST
Alternate-Protocol: 80:quic
Client-Date: Sat, 01 Feb 2014 04:38:00 GMT
Client-Peer: 74.125.235.160:80
Client-Response-Num: 1
P3P: CP="This is not a P3P policy! See http://support.google.com/accounts/bin/answer.py?answer=151657&hl=ja-JP for more info."
Set-Cookie: YSC=hEFCdovBRI0; path=/; domain=.youtube.com; httponly
Set-Cookie: PREF=f1=50000000; path=/; domain=.youtube.com; expires=Thu, 02-Oct-2014 16:25:10 GMT
Set-Cookie: VISITOR_INFO1_LIVE=DnbZPuvLZ5I; path=/; domain=.youtube.com; expires=Thu, 02-Oct-2014 16:25:10 GMT
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block; report=https://www.google.com/appserve/security-bugs/log/youtube

Cache-Control: no-cache
Expires: Tue, 27 Apr 1971 19:44:06 EST

$ HEAD http://stackoverflow.com/questions/18947314/how-to-add-a-linked-source-folder-in-android-studio
200 OK
Cache-Control: public, max-age=60
Connection: close
Date: Sat, 01 Feb 2014 04:33:35 GMT
Vary: *
Content-Length: 63568
Content-Type: text/html; charset=utf-8
Expires: Sat, 01 Feb 2014 04:34:36 GMT
Last-Modified: Sat, 01 Feb 2014 04:33:36 GMT
Client-Date: Sat, 01 Feb 2014 04:39:26 GMT
Client-Peer: 198.252.206.16:80
Client-Response-Num: 1
X-Frame-Options: SAMEORIGIN

Cache-Control: max-age=60
Expires: Sat, 01 Feb 2014 04:34:36 GMT

$ HEAD http://エロサイトコンテンツ
200 OK
Cache-Control: public, s-maxage=1260
Connection: close
Date: Sat, 01 Feb 2014 04:31:50 GMT
Via: 1.1 varnish
Age: 0
Server: nginx
Content-Type: text/html; charset=UTF-8
Client-Date: Sat, 01 Feb 2014 04:37:40 GMT
Client-Peer: 31.192.116.24:80
Client-Response-Num: 1
Set-Cookie: sid=4187441776523501568; path=/; expires=Tue, 31-Dec-2030 12:00:00 GMT; domain=.エロドメイン.com
Set-Cookie: is_pc=1; path=/; expires=Tue, 31-Dec-2030 12:00:00 GMT; domain=.エロドメイン.com
Set-Cookie: country=JP; path=/; domain=.エロドメイン.com
X-Varnish: 3954156791

Cache-Control: s-max-age=1260(varnish 向け)
Expires: blank
Varnish あり

$ HEAD http://en.wikipedia.org/wiki/HTTP_2.0
200 OK
Cache-Control: private, s-maxage=0, max-age=0, must-revalidate
Connection: close
Date: Sat, 01 Feb 2014 04:33:16 GMT
Via: 1.1 varnish, 1.1 varnish
Age: 5700
Server: Apache
Vary: Accept-Encoding,Cookie
Content-Language: en
Content-Type: text/html; charset=UTF-8
Last-Modified: Mon, 06 Jan 2014 09:29:06 GMT
Client-Date: Sat, 01 Feb 2014 04:39:06 GMT
Client-Peer: 208.80.154.224:80
Client-Response-Num: 1
X-Cache: cp1067 hit (5), cp1054 frontend hit (1)
X-Content-Type-Options: nosniff
X-Powered-By: PHP/5.3.10-1ubuntu3.9+wmf1
X-Varnish: 2829631474 2827122309, 1251628819 1244997693
X-Vary-Options: Accept-Encoding;list-contains=gzip,Cookie;string-contains=enwikiToken;string-contains=enwikiLoggedOut;string-contains=forceHTTPS;string-contains=enwikiSession;string-contains=centralauth_Token;string-contains=centralauth_Session;string-contains=centralauth_LoggedOut;string-contains=mf_useformat;string-contains=stopMobileRedirect

Cache-Control: max-age=0, s-maxage=0(Varnish 向け)
Expires: blank
Varnish あり

$ HEAD http://www.1101.com/suimin/samma/index.html
200 OK
Connection: close
Date: Sat, 01 Feb 2014 04:35:02 GMT
Via: 1.1 varnish
Age: 680137
ETag: "11f057d-b69-4480cd52ac500"
Server: Apache
Content-Type: text/html
Last-Modified: Mon, 10 Mar 2008 03:31:00 GMT
Client-Date: Sat, 01 Feb 2014 04:40:51 GMT
Client-Peer: 116.214.86.27:80
Client-Response-Num: 1
X-Varnish: 2679230077 2574494562

Cache-Control: max-age=0
Expires: blank
Varnish あり

$ HEAD http://cookpad.com/pr/contest/index/495
200 OK
Cache-Control: max-age=0, private, must-revalidate
Connection: Close
Date: Sat, 01 Feb 2014 04:33:59 GMT
ETag: "84f438827d94afb908434e1a21064610"
Server: nginx
Vary: User-Agent
Content-Type: text/html; charset=utf-8
Client-Date: Sat, 01 Feb 2014 04:39:49 GMT
Client-Peer: 54.249.90.123:80
Client-Response-Num: 1
Set-Cookie: cpb=00ncva0n099ac3e2370668444ea0a1209980bf9ecc7cb0036f; path=/; expires=Tue, 19-Jan-2038 03:14:07 GMT
Set-Cookie: v=315-4521644-9159558; path=/; expires=Tue, 19-Jan-2038 03:14:07 GMT
Set-Cookie: FVD=%7Bts+%272014-02-01+13%3A33%3A59%27%7D; path=/; expires=Mon, 03-Mar-2014 04:33:59 GMT
Set-Cookie: f_unique_id=15632df6-4323-464f-81a2-b1889828c59c; path=/; expires=Wed, 01-Feb-2034 04:33:59 GMT
Set-Cookie: cids=495; path=/
Set-Cookie: user_type=0; path=/; expires=Mon, 03-Mar-2014 04:33:59 GMT
Set-Cookie: has_kitchen=0; path=/; expires=Mon, 03-Mar-2014 04:33:59 GMT
Set-Cookie: trial_type=0; path=/; expires=Mon, 03-Mar-2014 04:33:59 GMT
Set-Cookie: _myapp_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTQxMzViNmY5NmIzYTVkNGM2MWM5ZTkwMjhhNzk2OTVhBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW9IRFc3THUyUkNnbWRLdXNGb1MrSW9Yd2djSzN6YnU0azcrcjhSbytxOUk9BjsARg%3D%3D--4ca3911027db73825d5fd98b353ff2cb11578e16; path=/; HttpOnly
Set-Cookie: withoutvns=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
Status: 200 OK
X-ASC: 159
X-ASN: 159
X-Chst: 32a6df64098aa9ada256c6e881941985
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Request-Id: d87e903120c048d56784636a9c608a45
X-Runtime: 0.287018
X-XSS-Protection: 1; mode=block

Cache-Control: max-age=0
Expires: blank

$ HEAD http://www.w3.org/Protocols/rfc2616/rfc2616.html
200 OK
Cache-Control: max-age=21600
Connection: close
Date: Sat, 01 Feb 2014 04:34:20 GMT
Accept-Ranges: bytes
ETag: "805b-3e3073913b100"
Server: Apache/2
Content-Length: 32859
Content-Type: text/html; charset=iso-8859-1
Expires: Sat, 01 Feb 2014 10:34:20 GMT
Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT
Client-Date: Sat, 01 Feb 2014 04:40:10 GMT
Client-Peer: 128.30.52.37:80
Client-Response-Num: 1
P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml"

Cache-Control: max-age=21600
Expires: Sat, 01 Feb 2014 10:34:20 GMT

$ HEAD  http://tokyo.craigslist.jp/act/4313870292.html
200 OK
Cache-Control: max-age=300, public
Connection: close
Date: Sat, 01 Feb 2014 03:49:13 GMT
Server: Apache
Vary: Accept-Encoding
Content-Type: text/html; charset=iso-8859-1
Expires: Sat, 01 Feb 2014 03:54:13 GMT
Last-Modified: Sat, 01 Feb 2014 03:49:13 GMT
Client-Date: Sat, 01 Feb 2014 04:49:50 GMT
Client-Peer: 208.82.238.226:80
Client-Response-Num: 1
Client-Transfer-Encoding: chunked
X-Frame-Options: SAMEORIGIN
X-MCP-Cache-Control: max-age=2592000, public

Cache-Control: max-age=300
Expires: Sat, 01 Feb 2014 03:54:13 GMT

$ HEAD http://nginx.org/en/docs/http/ngx_http_headers_module.html
200 OK
Connection: close
Date: Sat, 01 Feb 2014 04:34:46 GMT
Accept-Ranges: bytes
ETag: "528dfca7-1f0c"
Server: nginx/1.5.7
Content-Length: 7948
Content-Type: text/html; charset=utf-8
Last-Modified: Thu, 21 Nov 2013 12:29:27 GMT
Client-Date: Sat, 01 Feb 2014 04:40:35 GMT
Client-Peer: 206.251.255.63:80
Client-Response-Num: 1

Cache-Control: blank
Expires: blank

References

2 thoughts on “レスポンスヘッダーのExpires: -1って何?

  1. Pingback: キャッシュ

Leave a reply to キャッシュ Cancel reply