浏览器缓存(RFC7234)

Posted 若北

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器缓存(RFC7234)相关的知识,希望对你有一定的参考价值。

http缓存

译注:本文翻译自: https://www.rfc-editor.org/rf...
非英语专业,仅作参考使用。如果英语不错的建议看原文。
文中许多概念是直译的,可能不太好理解,可以阅读关于浏览器缓存的总结。

1、概述

HTTP通常用于可以通过使用响应缓存来提高性能的分布式信息系统。 本文档定义了HTTP / 1.1中缓存和重用响应消息相关的内容

HTTP缓存是响应消息的本地存储并且也是控制存储,检索和删除消息的子系统。 缓存存储可缓存的响应,以减少未来等效请求的响应时间和网络带宽消耗。除了隧道服务器, 任何客户端或服务器都可以使用缓存

共享缓存是一种被多位用户重用的存储响应。共享缓存通常(但并不总是)作为传输媒介(intermediary)的一部分。
相反,私有缓存,则专门用于同一用户(single user),他们通常被部署为用户代理组件

HTTP / 1.1缓存的目标是通过重用先前的响应消息以满足当前请求,来显着提高性能。 如果响应可以在没有“验证”的情况下重用 ,那么每次重用时都会减少延迟和网络开销。 当缓存的响应不够新(fresh)时,如果可以通过验证(第4.3节)或来源无法响应时(unavilable)(第4.2.4节),仍然可以重复使用


2、缓存操作概述

恰当的缓存操作保留HTTP传输的语义([RFC7231]),同时避免对缓存中已存在的信息进行传输。 虽然缓存是HTTP的完全可选功能,但是可以假设重用缓存的响应是可取的,
在没有规定或本地配置要求时,这种重用是默认行为。 因此,HTTP缓存规范的重点在于阻止缓存存储不可复用的响应或者不恰当的响应,而非总是存储缓存或者重用特定响应

每条缓存记录由缓存键和与使用相同键的先前请求相对应的一个或多个HTTP响应组成。 最常见的缓存记录是检索请求的成功结果。换言之,就是一个响应为200,并包含被请求目标进行资源标示描述的GET请求。([RFC7231]第4.3.1节) )。 不过,也可以缓存永久重定向,否定结果(例如,404(Not Found)),不完整的结果(例如,206(Partial Content)),并且除了GET以外也包含,明确允许此类缓存且定义了可以用作缓存键值的响应方法

主缓存键由请求方法和目标URI组成。 但是,由于如今通常使用的HTTP缓存通常限制于GET响应缓存,因此许多缓存只需拒绝其他方法并仅使用URI作为主缓存键

如果请求目标受内容协商影响,则其缓存记录可能包含多个响应存储内容,每个存储响应由原始请求选择标题字段的值作为辅助密钥来进行区分(第4.1节)


3、在缓存中存储响应

除了以下情况,缓存不应该存储任何请求的响应:
缓存可以理解请求方法,并将其定义为可缓存的,且
缓存中理解响应状态代码,以及
在请求或者响应的字段中没有显示的指出"no-store"缓存指令(请参阅第5.2节)
如果缓存是共享的,则“private”响应指令(参见第5.2.2.6节)不应出现在响应中。
除非响应明确允许(参见第3.2节),否则,共享缓存不应该出现Authorization字段(参见[RFC7235]的第4.2节),以及
响应应满足
包含Expires头部字段(参见第5.3节),或
包含max-age响应指令(参见第5.2.2.8节),或
包含s-maxage响应指令(参见第5.2.2.9节),用于共享缓存,或者
包含允许对其进行缓存的“Cache Control Extension”(请参阅第5.2.3节),或
具有默认可缓存的状态代码(参见第4.2.2节),或
包含public响应指令(参见第5.2.2.5节)
请注意,上面列出的任何要求都可以被缓存控制扩展覆盖; 见第5.2.3节
在此上下文中,缓存可以识别已确认实现了缓存相关行为的请求方法或者响应状态码
注意,在通常情况下如果既没有缓存验证,也没有明确过期时间的无效响应存储将不会被缓存。不过缓存也不会禁止这样的响应存储

3.1 存储不完整响应

在连接被关闭时,当所有被消息帧被标记为接受,那么这个响应消息被认为是完整的(RFC7230)。如果一个请求方法是GET,响应状态码为200(OK),并且已接收到整个响应标头部分,如果缓存条目被记录为不完整,则缓存可以存储不完整的响应消息正文。 同样地,可以存储206(Partial Content)响应,作为是不完整的200(OK)缓存条目。 但是,如果它不支持Range和Content-Range字段,或者它不了解这些字段中使用的范围单位,那么缓存就不能存储不完整或部分内容响应,

缓存可以通过使用子请求分段请求并且使用存储记录来组合成功响应使得响应请求完整(在3.3部分进行定义)。缓存不应该使用不完整的响应请求,除非该响应已经被组合完整或者该请求为一个指明了完整响应范围内的部分响应。在没有明确标记使用如206(Partial Content)状态码的情况下,缓存不应该发送部分响应给客户端。

3.2 存储Authenticated请求的响应

除非响应中存在存储首部字段,否则共享缓存不应该对包含Authorization首部字段的请求使用缓存响应
在本说明中,Cache-Control响应指令(第5.2.2节)应含有:must-revalidate,public和s-maxage。
需要注意的是,包含“must-revalidate”和/或“s-maxage”响应指令的缓存响应不允许共享缓存使用陈旧的响应(第4.2.4节)。需要特别指出的是,响应包含"max-age=0, must-revalidate"或者"s-maxage=0",在没有在源服务器上重新验证的情况下,不能用于满足后续请求。

3.3 合并部分内容

如果连接过早关闭或者请求使用了一个或者多个Range标示符响应将可能仅传输了部分表述(representation)。数次像这样的传输之后,缓存将可能收到同一表述的数个ranges。
如果他们全部共享相同的强制验证,并且满足4.3描述的客户端要求,缓存将可能合并这些ranges进同一个响应存储以复用于近期请求的响应。(4.3)
当使用一个或多个存储响应合并为一个新的响应时,缓存应满足
删除存储响应中包含1xx警告码的warning首部字段
保留存储响应中包含2xx警告码的warning首部字段
在新响应中使用除了Content-Range以外的首部字段去替换存储响应中的相应字段


4、构造缓存响应

在发起请求时,非以下情况缓存不得复用存储响应:
发送的URI请求有效([RFC7230]的第5.5节)并与存储的响应匹配。且
存储响应相关的请求方法允许它被用于发起的请求,且
选择相匹配被响应存储指定的首部字段,(参见第4.1节),且
除非存储响应已经被成功验证(4.3),否则发起的请求不应该包含no-cache指令(5.4),也不能包含no-cache缓存指令(5.2.1),且
除非存储响应已经被成功验证(4.3),否则发起的请求不应该包含no-cache指令(5.2.2.2),且
存储响应,也应当满足:
新鲜度(4.2),亦或者
允许陈旧的响应(4.2.4),亦或者
成功的通过验证(4.3)

请注意,上面列出的任何要求都可以被缓存控制扩展覆盖; 见第5.2.3节
当存储响应被用于满足一个未经验证的请求,缓存必须生成一个Age首部字段(5.1)来替代响应中等效的current_age(4.2.3)
缓存应当直写(write through)带有对源服务器不安全的方法(译注:非幂等)的请求([rfc7231]的第4.2.1节),也就是,在转发请求并收到相应回复之前,缓存不应当允许生成此类请求的回复
请注意,不安全的请求可能使得已存储的响应无效
当超过一个恰当的响应被存储,缓存必须使用最近期的响应(被Date首部字段所标明)。也可以使用带着"Cache-Control: max-age=0" 或 "Cache-Control: no-cache"首部字段转发请求去消除响应使用的歧义
如果缓存不在有效期(clock available)内,那么不再每次使用前进行验证的话,不能使用存储响应

4.1使用vary计算辅助键

译注:Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers).

当缓存收到了一个可以被带有Vary首部字段的存储响应所满足的请求([RFC7231]的第7.1.4节),除非全部是选择的首部字段(unless all of the selecting header),否则不应当使用该响应。被由Vary首部字段指定字段用于匹配原始请求(即,与存储响应相关联)与发送请求。
从俩次请求中选择的首部字段被定义用于匹配,当且仅当首次请求可以被应用以下方案变更为二次请求:
允许首部字段语法添加或移除空白
使用相同字段名称合并多个重复字段(参见[RFC7230的第3.2节)
根据标头字段的规范,以已知具有相同语义的方式对两个标头字段值进行规范化(例如,当顺序不重要时对字段值进行重新排序;大小写规范化,其中值定义为不区分大小写

如果(在任何可以进行处理的序列化之后),请求首部字段仍存在缺失,那么它只能匹配其他请求,即使它仍存在缺失
Vary值为"*",则始终匹配失败
使用选择头部字段匹配的存储响应作为被选择响应
如果多个响应可用(潜在的包含不携带Vary首部字段的响应),缓存将需要选择其中的一个进行使用,当一个选择首部字段拥有一种已知机制去这样做(例如,Accept中的qvalues值,以及相似的请求首部字段),那么该机制就可恶意被用作选择更优的响应。剩下的,将会通过Date首部字段根据最近日期选择一个最近期的响应,如第4章节所标明。
译注: Accept请求头中允许质量值语法 https://developer.mozilla.org...
质量值或q值和q因子用于描述以逗号分隔的列表中值的优先级顺序。这是某些HTTP标头和html中允许的特殊语法。值的重要性由后缀标记,\';q=\'后跟紧随其后的一个值,该值介于0和之间1,最多包含三个小数位,最高的值表示最高的优先级。如果不存在,则默认值为1。

如果没有可用的响应被选择,那么将不能使用缓存供给于发起的请求。通常,他将会转发(可能含有条件,详见4.3)请求至原始服务器服务器


4.2 新鲜度

新鲜的响应(fresh response)是指其存在周期(age)仍未超过新鲜度周期(freshness lifetime)。那么陈旧响应(stale response),就与其正好相反。
响应的新鲜度周期是介于其被源服务器创建到它过期之间的时间。一个明确的过期时间则是指源服务器不希望在未经验证过的情况下被缓存继续使用的时间。而当没有可用的过期时间时,则被缓存指定启发式过期时间
响应存在周期(age)是指其被创建或者成功通过验证以来存在的时间
如果一个响应是新鲜(fresh)的,那么他将可以在不与原始服务器取得链接的情况下满足后续请求,从而提升效率。
主要决定新鲜度的机制是源服务器使用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的缓存指令,以限制或放松对应响应的新鲜度计算

为了避免日期解析的共同问题,当计算新鲜度时应当:
即使所有的日期格式都被表明为大小写敏感,换内存仍应该使用大小写不敏感的方式匹配天、周、时区
如果缓存接收者的时间内部接口精确度小于HTTP-date,那么接收者的内部应当将将过期时间作为一个等同或者早于接收值的近似时间来解析
缓存收件者不应当让当地时区影响age或过期时间的计算或比较
缓存收件者应当将GMT或UTC以外的时区缩写看作是无效的
请注意,新鲜性仅适用于缓存操作; 它不能用于强制用户代理刷新其显示或重新加载资源。 有关缓存和历史机制之间的差异,请参阅第6节

4.2 计算新鲜度周期

缓存可以计算保鲜期(表示为 通过使用第一个匹配项来响应 下列的:
如果缓存是共享的,并且使用s-maxage response指令 (请参阅第5.2.2.9节),使用其值或
如果存在max-age响应指令(第5.2.2.8节), 使用其值,或
如果存在Expires响应标头字段(第5.3节),请使用 它的值减去Date响应标题字段的值,或
否则,当响应中没有明确指出过期时间。 将可能可能使用启发式的新鲜度周期;看 4.2.2节
请注意,由于所有 的信息来自原始服务器。 当给定指令存在多个值时 (例如,两个Expires标头字段,多个Cache-Control:max-age 指令),则该指令的值被视为无效。缓存将更倾向于认定该新鲜度无效的信息为已过期的

4.2.2 计算启发式新鲜度

由于原始服务器并不总是提供明确的到期时间, 当未指定明确的时间时,缓存可以使用使用其他标头字段的算法值(例如上次修改时间)分配启发式到期时间 ,以估算合理的到期时间值 。本规范未提供具体说明运算法则,但确实会对结果施加最坏情况的约束。
当存储的响应中存在明确的到期时间时,缓存不得使用启发式方法来确定新鲜度。 由于第3节中的要求,这意味着,启发式方法只能用于没有显式新鲜度的响应(默认情况下,状态码被定义为可缓存)(请参阅[RFC7231]第6.1节),而那些没有显式新鲜度的响应将 已被标记为可显式缓存(例如,使用“public”响应指令)
如果响应有一个last-modified的标题字段([RFC7232]的第2.2节),则鼓励缓存使用自那个时间以来不超过间隔的一小部分来作为启发式过期值。 这个分数的典型设置可能是10
译注:这里通常是指Last-Modified与Date差值的10%
使用启发式缓存策略时,如果超过当前时间 24 小时且从未警告过,浏览器或者代理服务器应该在响应中产生一个警告首部字段 Warning: 113 (5.5.4)。
注意:对于具有查询参数(即那些 包含“?”)的URI,[RFC2616]的13.9节禁止缓存计算启发式新鲜度。实际上,这尚未广泛实施。因此,鼓励源服务器对于不想缓存的情况发送明确的指令(例如Cache-Control:no-cache)

4.2.3 计算age

Age首部字段被用于描述一个缓存接收到响应消息的估算时长(Age)。Age 字段的值是指消息被源服务器创建或者验证之后以来缓存的秒数估算值。
重要的是,age值是响应沿源服务器的路径驻留在每个缓存中的时间的总和,并需要加上在网络路径中的传输时间
重要的是,age值是响应从源服务器在路径停驻每次缓存的总和值

以下事件被用于计算age

  • age_value:术语"age_value"以适合算数运算的形式表示Age首部字段,(5.1),如果不可用,则为0。
  • date_value: 术语“ date_value”以适合于算术运算的形式表示Date标头字段的值。 有关Date首部字段的定义以及关于响应不带Date值的规范,请参阅[RFC7231]的7.1.1.2节
  • now:术语“现在”表示“主机执行计算时钟的当前值”。 一个主机应该使用ntp([rfc5905])或一些类似的协议,使其时间协调同步UTC时间
  • request_time: 发起请求使得存储响应被触发的时间
  • response_time: 收到响应时主机时钟的当前值
    译注:
    ntp:网络时间协议
    Coordinated Universal Time(UTC):世界统一时间

响应的age可以以两种完全独立的方式计算
apparent_age:如果本地时钟与原始服务器的时钟是协调同步的,response_time减去date_value。 否则,结果将被零替换为零
corrected_age_value:如果沿响应路径的所有缓存实现HTTP / 1.1,缓存必须相对于启动请求的时间来解释此值,而非收到响应的时间

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首部字段的值置信(例如,没有HTTP / 1.0 hops存在于Via首部字段中),则在这种情况下,corrected_age_value可以用作corrected_initial_age
存储响应时间可以通添加存储响应最后一次被源服务器验证(以秒为单位)与corrected_initial_age的和值来计算

resident_time = now - response_time;  
current_age = corrected_initial_age + resident_time

4.2.4陈旧响应

“陈旧的”响应要么具有明确的到期信息,要么允许计算启发式过期,且根据第4.2节中的计算,该响应不是新的。
如果协议指令明确禁止缓存生成陈旧响应(例如,“ no-store”或“ no-cache”缓存指令、“ MUST-revalidate”缓存-响应-指令、或适用的“ s-maxage”或“ proxy-revalidate”缓存-响应-指令; 请参阅第5.2.2节) ,则缓存必须不生成陈旧响应。
除非断开连接,否则缓存必须不发送过期响应。(也就是说,它不能联系原始服务器或者找到转发路径) ,或者明确允许这样做(例如,max-stale指令; 参见5.2.1节)。
在陈旧响应中缓存应该生成一个警告头字段,其中包含110 warn-code (参见第5.5.1节)。同样,如果缓存断开连接,缓存应该在陈旧响应中生成112 warn-code (参见第5.5.3节)。
当转发没有Age标头字段的响应时,即使响应已经过时,缓存也不应生成新的Warning标头字段。缓存不需要验证在传输过程中变得过时的响应。

4.3 验证

当缓存拥有一个或多个请求资源存储响应,但他们之中的任何一个都无法用于进行响应服务(比如说,由于他们不在新鲜,或者有的无法被选择,详见4.1)。可以使用条件请求机制1(rfc7232)转发请求给下一个入站服务器一个机会选择一个有效缓存用以响应。更新流程中的储存元数据,或者使用新的响应替换存储响应。这个过程被称为响应存储的验证或者重验。

4.3.1 发送验证请求

当发送条件请求进行缓存验证,缓存将中发送一个或多个包含来自存储响应验证元数据的条件首部字段,然后由收件人对其进行比较,以确定存储的响应是否等效于资源的当前表述。
其中一个验证器是在 Last-Modified header 字段([ RFC7232]的第2.2节)中给出的时间戳,该时间戳可用于 If-Modified-Since 头字段用于响应验证,或用于 If-Unmodified-Since 或 If-Range 头字段用于表示选择(也就是说,客户机指的是以前获得的带有该时间戳的表示)。
另一个验证器是在 ETag 头字段([ RFC7232]的2.3节)中给出的实体标记。一个或多个实体标记,表示一个或多个存储的响应,可以用于 If-None-Match 头部字段用于响应验证,或者用于 If-Match 或 If-Range 头部字段用于表示选择(即,客户端专门引用一个或多个以前使用列出的实体标记获得的表示)。

4.3.2 处理收到的验证请求

请求链中的每个客户机可能都有自己的缓存,因此中间体上的缓存通常从其他(出站)缓存接收条件请求。同样,一些用户代理使用条件请求将数据传输限制到最近修改的表示或完成部分检索表示的传输。

译注:原文中第五章有如cache-control等首部字段指令的详细定义。这部分内容mdn有更专业的翻译,因此建议参考mdn

以上是关于浏览器缓存(RFC7234)的主要内容,如果未能解决你的问题,请参考以下文章

Net DB Web多级缓存的实现

[入门篇] HTTP 缓存 概念

HTTP/2 中的 HTTP 语义

RFC2616 13.3,浏览器历史和缓存

HTTP及RFC解析。

URL片段的最大长度(哈希)