协商缓存相关头部 Last-Modified 与 ETag 生成算法

1,250 阅读1分钟

Last-Modified

针对静态资源而言,一般会选择文件的 mtime 元属性作为上次修改时间,该元属性表示文件内容的修改时间。

nginx 也是这样处理的,源码见: ngx_http_static_module.c

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = of.size;
    r->headers_out.last_modified_time = of.mtime;

但是,Last-Modified 拥有一定的局限性

  1. 时间精读只有秒级别,但某文件在一秒内被更改 N 次
  2. 某一文件经过修改后,但是文件内容并未发生变化,比如添加一行再删除该行

ETag

ETag 拥有比 Last-Modified 更高的精读,从 HTTP 规范上来讲,可以解决以上问题。

比如针对文件内容进行 hash 计算,得到 hash 值作为 ETag 的值。

但是从实现上而言,往往不使用会消耗大量 CPU 的 hash 计算,比如 nginx,源码见:ngx_http_core_modules.c

etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
                                  r->headers_out.last_modified_time,
                                  r->headers_out.content_length_n)
                      - etag->value.data;

nginx 中 ETag 由响应头的 Last-Modified 与 Content-Length 表示为十六进制组合而成

$ docker exec -it $(docker run -d nginx:alpine) curl --head localhost
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Tue, 13 Sep 2022 09:24:39 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 21 Jun 2022 17:06:59 GMT
Connection: keep-alive
ETag: "62b1fab3-267"
Accept-Ranges: bytes

正因如此,即便 http 响应头中 ETag 值改变了,也不意味着文件内容一定已更改

实例

百度首页使用了 Nginx 类似的 ETag 生成算法,我在 Apifox 中进行了演示。见文档

作业

  1. ETag 值是如何生成的
  2. ETag 与 Last-Modified 有何区别
  3. 如果 http 响应头中 ETag 值改变了,是否意味着文件内容一定已经更改