Poison

Cache-Control: max-age

一直以为 max-age 设置的值表示本地浏览器接收到响应后从当前时刻开始计算的可存活的时间,今天查了个问题,发现并不是。根据 RFC 中的描述,max-age 表示自源站服务器端生成响应后的最大生存时间,并不是自本地浏览器接收到响应后的最大生存时间。

A response’s age can be calculated in two entirely independent ways:

  1. now minus date_value, if the local clock is reasonably well synchronized to the origin server’s clock. If the result is negative, the result is replaced by zero.
  2. age_value, if all of the caches along the response path implement HTTP/1.1.

Given that we have two independent ways to compute the age of a response when it is received, we can combine these as corrected_received_age = max(now - date_value, age_value) and as long as we have either nearly synchronized clocks or all HTTP/1.1 paths, one gets a reliable (conservative) result.

同时发现对没有设置 Cache-Control 的资源,浏览器对部分资源进行了 If-Modified-Since 探测,而部分资源没有进行探测直接使用了本地缓存,查了下文档,原来是根据资源的 Last-Modified 距当前时间的差值来决定是否缓存一段时间,简单来说就是如果一个资源上次修改的时间已经很久了,那么我们可以认为在一定时间段内,该资源不会被修改,所以这时即使没有设置 Cache-Control,浏览器也会将该资源缓存一段时间。典型的缓存时间计算公式为 (current time - last modified time) / 10

附上 Firefox 新鲜度计算的代码 gecko-dev/nsHttpResponseHead.cpp at esr102 · mozilla/gecko-dev · GitHub:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
// response as follows:
//
// freshnessLifetime = max_age_value
// <or>
// freshnessLifetime = expires_value - date_value
// <or>
// freshnessLifetime = min(one-week,
// (date_value - last_modified_value) * 0.10)
// <or>
// freshnessLifetime = 0
//
nsresult nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t* result) {
RecursiveMutexAutoLock monitor(mRecursiveMutex);
*result = 0;

// Try HTTP/1.1 style max-age directive...
if (NS_SUCCEEDED(GetMaxAgeValue_locked(result))) return NS_OK;

*result = 0;

uint32_t date = 0, date2 = 0;
if (NS_FAILED(GetDateValue_locked(&date))) {
date = NowInSeconds(); // synthesize a date header if none exists
}

// Try HTTP/1.0 style expires header...
if (NS_SUCCEEDED(GetExpiresValue_locked(&date2))) {
if (date2 > date) *result = date2 - date;
// the Expires header can specify a date in the past.
return NS_OK;
}

// These responses can be cached indefinitely.
if ((mStatus == 300) || (mStatus == 410) ||
nsHttp::IsPermanentRedirect(mStatus)) {
LOG(
("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
"Assign an infinite heuristic lifetime\n",
this));
*result = uint32_t(-1);
return NS_OK;
}

if (mStatus >= 400) {
LOG(
("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
"Do not calculate heuristic max-age for most responses >= 400\n",
this));
return NS_OK;
}

// From RFC 7234 Section 4.2.2, heuristics can only be used on responses
// without explicit freshness whose status codes are defined as cacheable
// by default, and those responses without explicit freshness that have been
// marked as explicitly cacheable.
// Note that |MustValidate| handled most of non-cacheable status codes.
if ((mStatus == 302 || mStatus == 304 || mStatus == 307) &&
!mCacheControlPublic && !mCacheControlPrivate) {
LOG((
"nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
"Do not calculate heuristic max-age for non-cacheable status code %u\n",
this, unsigned(mStatus)));
return NS_OK;
}

// Fallback on heuristic using last modified header...
if (NS_SUCCEEDED(GetLastModifiedValue_locked(&date2))) {
LOG(("using last-modified to determine freshness-lifetime\n"));
LOG(("last-modified = %u, date = %u\n", date2, date));
if (date2 <= date) {
// this only makes sense if last-modified is actually in the past
*result = (date - date2) / 10;
const uint32_t kOneWeek = 60 * 60 * 24 * 7;
*result = std::min(kOneWeek, *result);
return NS_OK;
}
}

LOG(
("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
"Insufficient information to compute a non-zero freshness "
"lifetime!\n",
this));

return NS_OK;
}
Reference

RFC 2616 - Hypertext Transfer Protocol – HTTP/1.1 - 13.2.3 Age Calculations
RFC 7234 - Hypertext Transfer Protocol (HTTP/1.1): Caching - 4.2.2. Calculating Heuristic Freshness
http - What happens if you don’t set cache-control header? - Webmasters Stack Exchange