4.2. 新鲜度 (Freshness)
新鲜的(fresh)响应是其年龄(age)尚未超过其新鲜度生命周期(freshness lifetime)的响应。相反,陈旧的(stale)响应是其年龄已经超过了新鲜度生命周期的响应。
响应的新鲜度生命周期是源服务器生成该响应到其过期时间(expiration time)之间的时间长度。显式过期时间(explicit expiration time)是源服务器打算让已存储的响应在该时间之后不能再被缓存使用、而必须进一步验证的时间点;而启发式过期时间(heuristic expiration time)是在没有可用的显式过期时间时由缓存分配的。
响应的年龄是自其由源服务器生成,或成功与源服务器验证以来经过的时间。
当响应在缓存中是"新鲜"的时候,它可以用于满足后续的请求,而无需联系源服务器,从而提升效率。
确定新鲜度的主要机制是源服务器使用 Expires 头字段(第 5.3 节)或 max-age 响应指令(第 5.2.2.8 节)提供一个未来的显式过期时间。通常,源服务器会为响应分配未来的显式过期时间,因为它认为表示在到达过期时间之前不太可能发生具有语义意义的变化。
如果源服务器希望强制缓存对每个请求都进行验证,它可以分配一个过去的显式过期时间,以指示该响应已经陈旧。符合要求的缓存通常会在为后续请求重用陈旧的缓存响应之前对其重新验证(见第 4.2.4 节)。
由于源服务器并不总是提供显式过期时间,缓存在某些情况下也被允许使用启发式方法来确定过期时间(见第 4.2.2 节)。
用于确定响应是否新鲜的判断为:
response_is_fresh = (freshness_lifetime > current_age)
freshness_lifetime 在第 4.2.1 节中定义;current_age 在第 4.2.3 节中定义。
客户端可以在请求中发送 max-age 或 min-fresh 缓存指令,以约束或放宽对应响应的新鲜度计算(第 5.2.1 节)。
在计算新鲜度时,为避免日期解析中的常见问题:
- 尽管所有日期格式都被指定为区分大小写,缓存接收方应该(SHOULD)以不区分大小写的方式匹配星期几、周和时区的名称。
- 如果缓存接收方的内部时间实现的分辨率低于 HTTP-date 的值,接收方必须(MUST)在内部将解析出的 Expires 日期表示为等于或早于接收值的最近时间。
- 缓存接收方必须不(MUST NOT)允许本地时区影响年龄或过期时间的计算或比较。
- 缓存接收方应该(SHOULD)将带有 GMT 或 UTC 之外的时区缩写的日期视为对过期计算无效。
注意,新鲜度只适用于缓存操作;它不能用来强制用户代理刷新其显示或重新加载资源。关于缓存与历史机制(history mechanisms)之间区别的说明,见第 6 节。
4.2.1. 计算新鲜度生命周期 (Calculating Freshness Lifetime)
缓存可以通过以下第一项匹配来计算响应的新鲜度生命周期(记为 freshness_lifetime):
- 如果缓存是共享缓存且存在 s-maxage 响应指令(第 5.2.2.9 节),则使用其值,或
- 如果存在 max-age 响应指令(第 5.2.2.8 节),则使用其值,或
- 如果存在 Expires 响应头字段(第 5.3 节),则使用其值减去 Date 响应头字段的值,或
- 否则,响应中不存在显式过期时间。可能适用启发式的新鲜度生命周期;见第 4.2.2 节。
注意,此计算不容易受到时钟偏差(clock skew)的影响,因为所有信息都来自源服务器。
当某个给定指令存在多个值(例如,两个 Expires 头字段、多个 Cache-Control: max-age 指令)时,该指令的值被视为无效。鼓励缓存将具有无效新鲜度信息的响应视为陈旧。
4.2.2. 计算启发式新鲜度 (Calculating Heuristic Freshness)
由于源服务器并不总是提供显式过期时间,当未指定显式时间时,缓存可以(MAY)分配一个启发式的过期时间,采用利用其他头字段值(例如 Last-Modified 时间)来估计一个合理的过期时间的算法。本规范不提供具体的算法,但确实对其结果施加了最坏情况下的约束。
当已存储的响应中存在显式过期时间时,缓存必须不(MUST NOT)使用启发式方法确定新鲜度。由于第 3 节的要求,这实际上意味着启发式只能用于那些没有显式新鲜度、且其状态码被默认定义为可缓存的响应(见 [RFC7231] 的第 6.1 节),以及那些没有显式新鲜度但已被标记为显式可缓存(例如带有 "public" 响应指令)的响应。
如果响应具有 Last-Modified 头字段([RFC7232] 的第 2.2 节),鼓励缓存使用不超过自该时间以来间隔的某个比例的启发式过期值。该比例的典型设置可能是 10%。
当使用启发式计算新鲜度生命周期时,如果其 current_age 超过 24 小时且此类警告尚不存在,缓存应该(SHOULD)在响应中生成一个带有 113 warn-code 的 Warning 头字段(见第 5.5.4 节)。
注意: [RFC2616] 的第 13.9 节禁止缓存为带有查询组件(即包含 '?')的 URI 计算启发式新鲜度。在实践中,这并未被广泛实现。因此,如果源服务器希望阻止缓存,鼓励其发送显式指令(例如
Cache-Control: no-cache)。
4.2.3. 计算年龄 (Calculating Age)
Age 头字段用于传达从缓存获取响应消息时的估计年龄。Age 字段值是缓存对自响应由源服务器生成或验证以来经过秒数的估计。本质上,Age 值是响应在从源服务器到这里的路径上所经过的每个缓存中的驻留时间,加上它在网络路径上传输所花费的时间之和。
以下数据用于年龄计算:
age_value
术语
age_value表示 Age 头字段(第 5.1 节)的值,采用适合算术运算的形式;如果不可用,则为 0。
date_value
术语
date_value表示 Date 头字段的值,采用适合算术运算的形式。Date 头字段的定义以及关于缺少该字段的响应的要求,见 [RFC7231] 的第 7.1.1.2 节。
now
术语
now表示"执行计算的主机当前时钟的值"。主机应该(ought to)使用 NTP([RFC5905])或某种类似的协议将其时钟同步到协调世界时(Coordinated Universal Time)。
request_time
产生已存储响应的请求发出时,主机时钟的当前值。
response_time
接收到响应时,主机时钟的当前值。
响应的年龄可以通过两种完全独立的方式计算:
-
"表观年龄"(apparent_age):response_time 减去 date_value,前提是本地时钟与源服务器的时钟 reasonably 同步。如果结果为负,则替换为 0。
-
"校正年龄值"(corrected_age_value):如果响应路径上的所有缓存都实现了 HTTP/1.1。缓存必须(MUST)相对于请求发起的时间(而非响应收到的时间)来解释此值。
apparent_age = max(0, response_time - date_value);
response_delay = response_time - request_time;
corrected_age_value = age_value + response_delay;
二者组合为:
corrected_initial_age = max(apparent_age, corrected_age_value);
除非缓存对 Age 头字段的值有信心(例如,因为 Via 头字段中没有 HTTP/1.0 跳),在这种情况下校正年龄值(corrected_age_value)可以(MAY)用作校正初始年龄(corrected_initial_age)。
然后,可以通过将已存储响应自上次由源服务器验证以来经过的时间(以秒为单位)加到校正初始年龄上,来计算已存储响应的当前年龄(current_age):
resident_time = now - response_time;
current_age = corrected_initial_age + resident_time;
4.2.4. 提供陈旧响应 (Serving Stale Responses)
"陈旧"(stale)响应是指具有显式过期信息或被允许计算启发式过期,但根据第 4.2 节的计算不新鲜的响应。
如果被显式的协议内指令禁止(例如,被 "no-store" 或 "no-cache" 缓存指令、"must-revalidate" 缓存响应指令,或适用的 "s-maxage" 或 "proxy-revalidate" 缓存响应指令禁止;见第 5.2.2 节),缓存必须不(MUST NOT)生成陈旧响应。
除非缓存处于断开连接状态(即它无法联系源服务器或无法找到前向路径),或者这样做被明确允许(例如,被 max-stale 请求指令允许;见第 5.2.1 节),否则缓存必须不(MUST NOT)发送陈旧响应。
缓存应该(SHOULD)在陈旧响应中生成带有 110 warn-code 的 Warning 头字段(见第 5.5.1 节)。同样地,如果缓存已断开连接,缓存应该(SHOULD)在陈旧响应中生成 112 warn-code(见第 5.5.3 节)。
即使是已经陈旧的响应,缓存在转发没有 Age 头字段的响应时,也应该不(SHOULD NOT)生成新的 Warning 头字段。缓存无需验证仅仅在传输过程中变陈旧的响应。