2. 验证器 (Validators)
本规范定义两种常用于观察资源状态并测试前置条件的元数据形式: 修改日期 (第 2.2 节) 和不透明实体标签 (第 2.3 节). 反映资源状态的其他元数据已经由 HTTP 的各种扩展定义, 例如 Web Distributed Authoring and Versioning (WebDAV, [RFC4918]), 但这些内容超出本规范范围. 当资源元数据值在前置条件中使用时, 称为 "验证器" (validator).
2.1. 弱与强 (Weak versus Strong)
验证器分为两类: 强验证器和弱验证器. 弱验证器易于生成, 但用于比较时价值低得多. 强验证器非常适合比较, 但可能很难高效生成, 有时甚至无法高效生成. HTTP 并不要求所有形式的资源都遵循同一强度的验证器, 而是公开所使用验证器的类型, 并限制弱验证器可作为前置条件使用的时机.
"强验证器" (strong validator) 是一种表示元数据: 只要表示数据发生可在 GET 的 200 (OK) 响应 payload body 中观察到的变化, 其值就会改变.
强验证器也可能因为表示数据变化以外的原因而改变, 例如表示元数据中具有语义意义的部分发生变化 (Content-Type 等). 不过, 只有在有必要使远程缓存和创作工具保存的响应失效时才改变该值, 最符合源服务器的利益.
无论过期时间如何, 缓存条目都可能保留任意长的时间. 因此, 缓存可能尝试使用很久以前获得的验证器来验证某个条目. 对于某一特定资源在一段时间内关联的所有表示的所有版本, 强验证器都是唯一的. 但是, 这并不意味着不同资源的表示之间也具有唯一性, 也就是说, 同一个强验证器可能同时用于多个资源的表示, 但并不表示这些表示等价.
相比之下, "弱验证器" (weak validator) 是一种不一定会随表示数据每次变化而改变的表示元数据. 这种弱性可能来自值计算方式的限制, 例如时钟分辨率; 也可能来自无法保证资源所有可能表示的唯一性; 或者来自资源所有者希望按自行确定的一组等价关系对表示分组, 而不是按唯一的数据序列分组. 当源服务器认为先前表示不能再作为当前表示的可接受替代品时, 源服务器 SHOULD 改变弱 entity-tag. 换言之, 当源服务器希望缓存使旧响应失效时, 弱 entity-tag 就应改变.
强验证器可用于所有条件请求, 包括缓存验证, 部分内容范围以及避免 "lost update". 弱验证器只能在客户端不要求与先前获得的表示数据完全相等时使用, 例如验证缓存条目或将 Web 遍历范围限制为最近更改的资源.
2.2. Last-Modified
响应中的 Last-Modified 头字段提供一个时间戳, 表示源服务器在请求处理结束时认定的选定表示最后修改日期和时间.
Last-Modified = HTTP-date
使用示例如下:
Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
2.2.1. 生成 (Generation)
对于能够合理且一致地确定最后修改日期的任何选定表示, 源服务器 SHOULD 发送 Last-Modified. 这是因为它在条件请求和缓存新鲜度评估 ([RFC7234]) 中的使用会显著减少 Internet 上的 HTTP 流量, 并且可能是提高服务可扩展性和可靠性的重要因素.
源服务器 SHOULD 在尽可能接近生成响应 Date 字段值的时间获取表示的 Last-Modified 值. 这使接收方能够准确评估表示的修改时间, 尤其是在表示接近响应生成时间发生变化时.
带有时钟的源服务器 MUST NOT 发送晚于服务器消息生成时间 (Date) 的 Last-Modified 日期. 如果最后修改时间来自特定于实现的元数据, 且按源服务器时钟计算为未来某个时间, 则源服务器 MUST 用消息生成日期替换该值. 这可以防止未来修改日期对缓存验证产生不利影响.
2.2.2. 比较 (Comparison)
Last-Modified 时间在请求中作为验证器使用时默认是弱的, 除非可以根据规范中定义的特定规则推断它是强的.
2.3. ETag
响应中的 ETag 头字段提供选定表示的当前 entity-tag, 该值在请求处理结束时确定. entity-tag 是一种不透明验证器, 用于区分同一资源的多个表示; 无论这些表示是由于资源状态随时间变化而产生, 还是由于内容协商导致多个表示同时有效, 或二者兼有, 都适用.
ETag = entity-tag
entity-tag = [ weak ] opaque-tag
weak = %x57.2F ; "W/", case-sensitive
opaque-tag = DQUOTE *etagc DQUOTE
etagc = %x21 / %x23-7E / obs-text
示例:
ETag: "xyzzy"
ETag: W/"xyzzy"
ETag: ""
entity-tag 可以是弱验证器或强验证器, 默认是强验证器. 如果源服务器为某个表示提供 entity-tag, 且该 entity-tag 的生成方式不满足强验证器的所有特征 (第 2.1 节), 则源服务器 MUST 通过在其不透明值前加上 W/ 前缀 (大小写敏感) 将该 entity-tag 标记为弱.
2.3.1. 生成 (Generation)
entity-tag 背后的原则是: 只有服务作者足够了解资源实现, 才能为该资源选择最准确且最高效的验证机制; 并且任何此类机制都可以映射为简单的 octet 序列以便比较. 由于该值是不透明的, 客户端无需知道每个 entity-tag 是如何构造的.
对于能够合理且一致地检测变化的任何选定表示, 源服务器 SHOULD 发送 ETag. 这是因为 entity-tag 在条件请求和缓存新鲜度评估 ([RFC7234]) 中的使用可以显著减少 HTTP 网络流量, 并且可能是提高服务可扩展性和可靠性的重要因素.
2.3.2. 比较 (Comparison)
根据比较上下文是否允许使用弱验证器, entity-tag 有两种比较函数:
- 强比较 (Strong comparison): 如果两个 entity-tag 都不是弱的, 且其 opaque-tag 逐字符匹配, 则二者等价.
- 弱比较 (Weak comparison): 如果两个 entity-tag 的 opaque-tag 逐字符匹配, 则二者等价, 无论其中一个或两个是否被标记为 "weak".
下面的示例展示一组 entity-tag 对的比较结果:
| ETag 1 | ETag 2 | 强比较 | 弱比较 |
|---|---|---|---|
| W/"1" | W/"1" | 不匹配 | 匹配 |
| W/"1" | W/"2" | 不匹配 | 不匹配 |
| W/"1" | "1" | 不匹配 | 匹配 |
| "1" | "1" | 匹配 | 匹配 |
2.4. 何时使用 Entity-Tags 和 Last-Modified 日期 (When to Use Entity-Tags and Last-Modified Dates)
在对 GET 或 HEAD 的 200 (OK) 响应中, 源服务器:
- SHOULD 发送 entity-tag 验证器, 除非生成它不可行.
- MAY 发送弱 entity-tag 而不是强 entity-tag, 如果性能考虑支持使用弱 entity-tag, 或者发送强 entity-tag 不可行.
- SHOULD 发送
Last-Modified值, 如果发送它可行.
换言之, 源服务器的首选行为是在检索请求的成功响应中同时发送强 entity-tag 和 Last-Modified 值.
客户端:
- 如果源服务器提供了 entity-tag, MUST 在任何缓存验证请求中 (使用
If-Match或If-None-Match) 发送该 entity-tag. - 如果源服务器只提供了
Last-Modified值, SHOULD 在非子范围缓存验证请求中 (使用If-Modified-Since) 发送该Last-Modified值. - 如果源服务器同时提供了 entity-tag 和
Last-Modified值, SHOULD 在缓存验证请求中同时发送这两个验证器. 这允许 HTTP/1.0 和 HTTP/1.1 缓存作出适当响应.