- 当文档在客户端“过期”之后(也就是说,客户端不再认为该副本有效),客户端必须从服务器请求一份新的副本。不过,如果该文档在服务器上并未发生改变,客户端也就不需要再接收一次了——继续使用缓存的副本即可。
- 这种特殊的请求,称为有条件的请求(conditional request),要求客户端使用验证码(validator)来告知服务器它当前拥有的版本号,并仅当它的当前副本不再有效时才要求发送新的副本。
1. 新鲜度
- 服务器应当告知客户端能够将内容缓存多长时间,在这个时间之内就是新鲜的。
- 服务器可以用这两个首部之一来提供这种信息: Expires(过期) 和 Cache-Control(缓存控制)。
- 缓存和新鲜度在第 7 章中曾有详细讨论。
- Cache-Control首部的指令:
指令 | 报文类型 | 描述 |
---|---|---|
no-cache | 请求 | 在重新向服务器验证之前,不要返回文档的缓存副本 |
no-store | 请求 | 不要返回文档的缓存副本。不要保存服务器的响应 |
max-age | 请求 | 缓存中的文档不能超过指定的使用期 |
max-stale | 请求 | 文档允许过期(根据服务器提供的过期信息计算),但不能超过指令中指定的过期值 |
min-fresh | 请求 | 文档的使用期不能小于这个指定的时间与它的当前存活时间之和。换句话说,响应必须至少在指定的这段时间之内保持新鲜 |
no-transform | 请求 | 文档在发送之前不允许被转换 |
only-if-cached | 请求 | 只有当文档在缓存中才发送,不要联系原始服务器 |
public | 响应 | 响应可以被任何服务器缓存 |
private | 响应 | 响应可以被缓存,但只能被单个客户端访问 |
no-cache | 响应 | 如果该指令伴随一个首部列表的话,那么内容可以被缓存并提供给客户端,但必须先删除所列出的首部。如果没有指定首部,缓存中的副本在没有重新向服务器验证之前不能提供给客户端 |
no-store | 响应 | 响应不允许被缓存 |
no-transform | 响应 | 响应在提供给客户端之前不能做任何形式的修改 |
must-revalidate | 响应 | 响应在提供给客户端之前必须重新向服务器验证 |
proxy-revalidate | 响应 | 共享的缓存在提供给客户端之前必须重新向原始服务器验证。私有的缓存可以忽略这条指令 |
max-age | 响应 | 指定文档可以被缓存的时间以及新鲜度的最长时间 |
s-max-age | 响应 | 指定文档作为共享缓存时的最长使用时间(如果有 max-age 指 令的话,以本指令为准)。私有的缓存可以忽略本指令 |
2. 有条件的请求与验证码
- 当请求缓存服务器中的副本时,如果它不再新鲜,缓存服务器就需要保证它有一个新鲜的副本。缓存服务器可以向原始服务器获取当前的副本。但在很多情况下,原始服务器上的文档仍然与缓存中已过期的副本相同。若此时依然去获取文档,会增加服务器不必要的负担。
- 为了避免这种情况,HTTP 为客户端提供了一种方法,仅当资源改变时才请求副本,这种特殊请求称为有条件的请求。有条件的请求是标准的 HTTP 请求报文,但仅当某个特定条件为真时才执行。
- 有条件的请求是通过以“If-”开头的有条件的首部来实现的。有条件的首部使得方法仅在条件为真时才执行。如果条件不满足,服务器就发回一个 HTTP 错误码。在第 7 章也有阐述。
- 每个有条件的请求都通过特定的验证码来发挥作用。验证码是文档实例的一个特殊属性,用它来测试条件是否为真。从概念上说,你可以把验证码看作文件的序列号、版本号,或者最后发生改变的日期时间。
- 比如:有条件的首部 If-Modified-Since 测试的是文档实例最后被修改的日期时间,因此我们说最后被修改的日期时间就是验证码。有条件的首部 If-None-Match 测试的是文档的 ETag 值,它是与实体相关联的一个特殊的关键字,或者说是版本识别标 记。Last-Modified 和 ETag 是 HTTP 使用的两种主要验证码。
- 下表列出了用于有条件请求的 4 种 HTTP 首部。每个有条件的首部之后就是这种首部所用的验证 码类型:
请求类型 | 验证码 | 描述 |
---|---|---|
If-Modified-Since | Last-Modified | 如果在前一条响应的 Last-Modified 首部中说明的时间之后,资源的版本发生变化,就发送其副本 |
If-Unmodified-Since | Last-Modified | 仅在前一条响应的 Last-Modified 首部中说明的时间之后,资源的版本没有变化,才发送其副本 |
If-Match | ETag | 如果实体的标记与前一次响应首部中的 ETag 相同,就发送该资源的副本 |
If-None-Match | ETag | 如果实体的标记与前一次响应首部中的 ETag 不同,就发送该资源的副本 |
- HTTP 把验证码分为两类:弱验证码(weak validators)和强验证码(strong validators)。弱验证码不一定能唯一标识资源的一个实例,而强验证码必须如此。弱验证码的一个例子是对象的大小字节数。有可能资源的内容改变了,而大小还保持不变,因此假想的字节计数验证码与改变是弱相关的。而资源内容的加密校验和(比如 MD5)就是强验证码,当文档改变时它总是会改变。
- 最后修改时间被当作弱验证码,因为尽管它说明了资源最后被修改的时间,但它的描述精度最大就是 1 秒。因为资源在 1 秒内可以改变很多次,而且服务器每秒可以处理数千个请求,最后修改日期时间并不总能反应变化情况。
- ETag 首部被当作强验证码,因为每当资源内容改变时,服务器都可以在 ETag 首部放置不同的值。版本号和摘要校验和也是很好的 ETag 首部候选,但它们不能带有任意的文本。ETag 首部很灵活,它可以带上任意的文本值(以标记的形式),这样就可以用来设计出各种各样的客户端和服务器验证策略。
- 有时候,客户端和服务器可能需要采用不那么精确的实体标记验证方法。例如,某服务器可能想对一个很大、被广泛缓存的文档进行一些美化修饰,但不想在缓存服务器再验证时产生很大的传输流量。在这种情况下,该服务器可以在标记前面加上“W/”前缀来广播一个“弱”实体标记。对于弱实体标记来说,只有当关联的实体在语义上发生了重大改变时,标记才会变化。而强实体标记则不管关联的实体发生了什么性质的变化,标记都一定会改变。
- 概括一下,当客户端多次访问同一个资源时,首先需要判断它当前的副本是不是仍然新鲜。如果不再新鲜,它们就必须从服务器获取最新的版本。为了避免在资源没有改变的情况下收到一份相同的副本,客户端可以向服务器发送有条件的请求,说明能唯一标识客户端当前副本的验证码。只在资源和客户端的副本不同的情况下服务器才会发送其副本。更多关于缓存再验证的细节,请回顾 7.5 节。