bigywater.tech
Django · 23

条件 GET、ETag 与 Last-Modified

ETag 和 Last-Modified 能让浏览器少拉内容,但「少拉」的是带宽还是计算?Django 有两条路:中间件自动算,和 @condition 提前判断。这篇数视图被执行的次数,把两条路的差别直接量出来。

视图仍执行
ConditionalGetMiddleware 自动生成 ETag 时,304 也是视图执行完之后才决定的
不执行视图
@condition 在视图之前比对,命中直接 304,视图调用次数不增
412
POST 带一个与当前 ETag 相同的 If-None-Match 得到 412,不是 304

中间件自动 ETag

实测 · 本机执行输出
第 1 次: status 200  ETag = "164c93b1e8f663c2439805c7f8474f14"  体 5000 字节  视图调用次数 1
带 If-None-Match: 304  体 0 字节  视图调用次数 2 (视图还是执行了)

中间件在响应阶段根据内容算出 ETag,再和请求头比较。视图已经跑完、响应已经生成,所以只能省下传输,省不了计算。

@condition

实测 · 本机执行输出
第 1 次 GET /etag/: 200 "v1"  视图调用 1
If-None-Match: "v1" → 304  体 0 字节  视图调用 1  304 响应头: {'ETag': '"v1"'}
If-None-Match: "v2" → 200  视图调用 2
If-None-Match: W/"v1"(弱校验器) → 304
If-None-Match: * → 304

etag_func / last_modified_func 在视图之前调用(应当是便宜的,比如读一个版本号或最后更新时间)。命中时直接返回 304,昂贵的视图不执行。

Last-Modified

实测 · 本机执行输出
第 1 次: 200  Last-Modified = Thu, 01 Jan 2026 12:00:00 GMT  视图调用 1
If-Modified-Since: Thu, 01 Jan 2026 12:00:00 GMT    → 304
If-Modified-Since: Thu, 01 Jan 2026 11:59:59 GMT    → 200
If-Modified-Since: Thu, 01 Jan 2026 12:00:01 GMT    → 304
If-Modified-Since: garbage                          → 200
视图累计调用 3

比较的是秒级时间。客户端传来不合法的日期时被当成没传。

两个条件一起用

实测 · 本机执行输出
If-None-Match "v1"  If-Modified-Since  01 Jan 2026 12:00:00… → 304
If-None-Match "v2"  If-Modified-Since  01 Jan 2026 12:00:00… → 200
If-None-Match "v1"  If-Modified-Since  01 Jan 2026 11:00:00… → 304
POST 带 If-None-Match: "v1" → 412

同时提供两个函数时,两个条件都参与判断;实测 ETag 匹配即 304(哪怕 If-Modified-Since 更早)。注意对不安全方法(POST)带 If-None-Match 的处理是 412。

分步演示:条件请求

条件 GET
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。视图调用次数用视图里的计数器统计
    • 没有验证:If-Range / Range 请求、强弱校验器的更细区别(本文只测了 W/ 前缀在 If-None-Match 下匹配)、真实浏览器的缓存行为(这里只用 Client 手工带头)、GZipMiddleware 对自动 ETag 的影响
    京ICP备2026067502号-1京公网安备11010502063487号