ETags 是“用于区分相同资源多个表示形式的“不透明验证器””。它们以各种方式用于 HTTP 中,以确定条件请求的结果,如 RFC 7232 中所述。完全理解 ETags 需要对 HTTP 以及资源及其表示形式的细微差别有非常全面的了解。本文档不试图一次解决 ETags 的所有应用,而是解决因其他指南而产生的特定用例。它将随着时间的推移而发展。
HTTP 基本上是一个在网络连接上传输资源表示形式的系统。一种常见的交互方式是GET /some/resource,修改表示形式,然后PUT /some/resource以更新服务器上的资源。这是一种非常有用的且表面上简单的模式,它驱动着 OpenStack 及其他许多 API。
这种看似简单的模式具有误导性:如果有两个或多个客户端在/some/resource上同时执行操作,他们可能会遇到 丢失更新问题
客户端 B 的更改丢失了。 任何客户端都没有意识到这一点。这是一个问题。
HTTP/1.1 及更高版本提供了一个名为 ETags 的解决方案。这些为资源的不同的表示形式提供了一个验证器,可以很容易地确定请求或响应提供的表示形式是否与已经拥有的表示形式相同。这在验证缓存的 GET 请求(ETag 回答的问题是“我缓存中的内容与服务器提供的内容相同吗?”)时非常有用,但也有助于避免丢失更新问题。
如果修改上述场景以使用 ETags,它将像这样工作
客户端 B 的更改没有丢失,客户端 A 没有无意中更改了与预期形式不符的内容。客户端 A 通过响应代码意识到这一点。在此阶段,客户端 A 可以选择再次 GET 资源,并将他们的本地表示形式与刚刚检索到的表示形式进行比较,并选择一个行动方案。
如果服务接受 PUT 请求并需要避免丢失更新,可以通过以下方式实现
注意
ETag 值是一个双引号字符串"the etag".
注意
If-Match header 可以包含多个 ETags(用逗号分隔)。如果包含多个 ETags,则至少有一个必须匹配才能使请求继续。
注意
代码库的哪个部分负责管理 ETag 和 If-Match header 在很大程度上取决于服务的架构。通常,每个资源的 handler 或 controller 应该是责任的中心。可能存在可以共享的装饰器或库,但这些内容超出了本文档的范围。早期实施者鼓励编写透明且易于检查的代码,以便于未来的提取。
注意
ETags 可以是强 ETag 或弱 ETag。请参阅 RFC 7232,了解有关弱 ETag 可能用法的讨论。它们在本文档中未涉及,因为它们的重要性主要与缓存处理有关。强 ETag 表示相同资源的表示形式之间存在字节对字节的等价性。弱 ETag 表示仅语义等价性。
上述每个步骤都需要功能来为表示形式生成 ETags。每当表示形式不同时,ETag 应该不同。RFC 7232 提供了有关如何生成良好 ETags 的建议。在实践中,它们应该是
理想情况下,它们应该计算速度快,或者如果速度不够快,则易于存储(在写入表示形式时)。最后更新时间戳和 content-type 的哈希值可以工作,但前提是更新频率低于时钟更新频率。
注意
ETags 如何有用的许多细节在本文档中未提及。值得阅读 RFC 7232 的全文,以了解其目的、工作方式、边缘情况以及它与其他条件请求处理模式的交互方式。
对于表示单个统一实体的简单资源,上述处理方法效果很好。对于更复杂的资源,情况会变得更加复杂。一些值得考虑的场景
当资源表示资源集合时(例如GET /resources与GET /resources/some-id),使用 ETags 更新该集合中的一个资源的严格过程将是
这可能被认为很繁琐。优化此的一种方法是在集合资源中包含一个其值为 ETag 的属性。然后,可以跳过上述第二个 GET,因为 ETag 已经可用。
当资源具有子资源时(例如,/image/id资源包含一个也可用在/image/id/metadata的元数据属性时),希望检索图像资源,然后 PUT 到元数据资源。严格来说,这将需要获取元数据资源以确定 ETag。
如果这是一个问题,则绕过此问题的一种优化方法是允许图像资源的 ETag 作为在If-Matchheader 中提供的元数据资源的有效 ETag。如果这样做,则重要的是反之不成立:在If-Matchheader 发送到图像资源时,元数据资源发送的 ETag 不应有效。
注意
在上述两种情况下,ETags 的语义都被违反了。ETag 不是解锁资源并使其可写的神奇密钥。它是一个用于确定相同资源的两个表示形式是否确实相同的值。在上述情况下,它们正在比较不同的资源。服务仅应这样做,如果必须这样做。因为性能优势巨大(在这种情况下,请考虑修复 API 的性能)或用户体验改进非常重要。后者比前者更重要和合法。