如何生成和配置 ETag?

How ETags are generated and configured?

我最近通过了 ETag HTTP header 的概念。 (this) 但我仍然有一个问题,对于负责生成 ETags 的特定 HTTP 资源?

换句话说就是实际应用、容器(Ex:Tomcat)、Web Server/Load balancer(Ex:Apache/Nginx)?

有人可以帮忙吗?

与 HTTP 规范的大多数方面一样,责任最终落在提供资源的人身上。

当然,在这种情况下,我们通常会使用工具——服务器、负载平衡器、应用程序框架等——来帮助我们履行这些职责。但是没有任何规范定义与应用程序相对的“网络服务器”应该提供什么,这只是一个实际问题,即您正在使用的工具中有哪些功能可用。

现在,特别是 ETags,一种常见的情况是框架或 Web 服务器可以配置为自动散列响应(正文或​​其他内容)并将结果放入 ETag。然后,根据条件请求,它将生成一个响应并对其进行散列以查看它是否已更改,如果没有更改则自动发送条件响应。

举两个我熟悉的例子,nginx can do this with static files at web server level, and Django can do this 在应用程序级别具有动态响应。

这种方法很常见,易于配置,而且效果很好。但是,在某些情况下,它可能不是最适合您的用例。例如:

  • 要计算哈希以与传入的 ETag 进行比较,您首先必须有一个响应。因此,虽然条件响应可以为您节省传输响应的开销,但它不能为您节省生成响应的成本。因此,如果生成响应的成本很高,并且您有 ETags 的替代来源(例如,存储在数据库中的版本号),则可以使用它来获得更好的性能。
  • 如果您打算使用 ETagsprevent accidental overwrites with state-changing methods,您可能需要添加自己的应用程序代码以使比较和设置逻辑原子化。

因此在某些情况下,您可能希望在应用程序级别创建 ETags。再次以 Django 为例,它提供了一种简单的方法让你 provide your own function 计算 ETags.

总而言之,为您控制的资源提供 ETags 最终是您的责任,但您也可以利用软件堆栈中的工具为您完成。

Web 服务器中使用的典型算法概述。 假设我们有一个包含

的文件
  • 大小 1047,即十六进制的 417。
  • MTime 即最后一次 mod 化于 2020 年 1 月 6 日星期一 12:54:56 GMT 在 Unix 时间中是 1578315296 秒或 1578315296666771000 纳秒。
  • Inode 是一个物理文件号 66,即十六进制的 42

不同的网络服务器returns ETag 如:

  • Nginx:"5e132e20-417""hex(MTime)-hex(Size)"。不可配置。
  • BusyBox httpd 与 Nginx 相同
  • monkey httpd 与 Nginx
  • 相同
  • Apache/2.2:"42-417-59b782a99f493""hex(INode)-hex(Size)-hex(MTime in nanoseconds)"。可以是 configured 但 MTime 无论如何都会以纳米为单位
  • Apache/2.4: "417-59b782a99f493""hex(Size)-hex(MTime in nanoseconds)" 即没有 INode,当相同的文件在不同的服务器上有不同的 INode 时,这有利于负载平衡。
  • OpenWrt uhttpd:"42-417-5e132e20""hex(INode)-hex(Size)-hex(MTime)"。不可配置。
  • Tomcat 9: W/"1047-1578315296666"Weak"Size-MTime in milliseconds"。这是 incorrect ETag 因为它对于静态文件应该是强大的,即八进制兼容性。
  • LightHTTPD:"hashcode(42-1047-1578315296666771000)"INode-Size-MTime 但随后通过哈希码 (dekhash) 简化为一个简单的整数。可以配置但只能禁用一部分(etag.use-inode = "disabled")
  • MS IIS:它有一个形式 Filetimestamp:ChangeNumber 例如“53dbd5819f62d61:0”。未记录,不可配置,但可以禁用。
  • Jetty:基于最后 mod、大小和散列。参见 Resource.getWeakETag()
  • 基图拉 (Swift): "W/hex(Size)-hex(MTime)" StaticFileServer.calculateETag

一些想法:

  • 这里经常使用十六进制数,因为将十进制数转换为较短的十六进制字符串的成本较低。
  • 如果您在应用程序重新部署期间简单地复制文件,则 Inode 在添加更多保证的同时会使负载平衡变得不可能并且非常脆弱。 MTime 纳秒级并非在所有平台上都可用,不需要这种粒度。
  • Apache 有一个类似 https://bz.apache.org/bugzilla/show_bug.cgi?id=55573
  • 的错误
  • MTime-SizeSize-MTime 的顺序也很重要,因为 MTime 更有可能发生变化,因此比较 ETag 字符串可能会更快一些 CPU 个周期。
  • 即使这不是完整的校验和哈希,但绝对不是弱 ETag。这足以表明我们期望 Range 请求的八进制兼容性。
  • Apache 和 Nginx 共享几乎所有互联网流量,但大多数静态文件通过 Nginx 共享并且不可配置。

看起来 Nginx 使用了最合理的架构,因此如果您实施,请尝试使其相同。 一行C语言生成的整个ETag:

printf("\"%" PRIx64 "-%" PRIx64 "\"", last_mod, file_size)

我的提议是采用 Nginx 模式并将其作为 recommended ETag algorithm by W3C