etag是资源的唯一实体标签,用于精确校验内容是否变更;flask不自动处理etag,需手动生成(如对关键参数哈希)并校验if-none-match头,否则缓存可能返回过期数据。

ETag 是什么,为什么 Flask 里要手动处理它
Flask 默认不自动生成或校验 ETag,哪怕你用了 make_response 或 send_file。浏览器发来 If-None-Match 请求头时,Flask 不会自动比对、返回 304 Not Modified——这事得你自己做。
常见错误是以为加了 response.cache_control.max_age = 3600 就够了,结果缓存没失效、数据改了用户还看到旧内容。根本原因是没提供强校验依据:ETag 才是告诉浏览器“这个资源是否真的一样”的唯一可靠凭证。
如何为动态响应生成可靠 ETag 值
不能用时间戳或随机数——它们无法反映内容实际变化;也不能直接用整个响应体的 hashlib.md5(body).hexdigest(),因为 body 可能是流式生成的、还没完全拼出来。
- 推荐方式:在视图逻辑中提前计算内容指纹,比如对数据库查询结果、模板参数、关键配置项做哈希
- 例如,若响应依赖
user_id和report_date,用hashlib.sha256(f"{user_id}:{report_date}".encode()).hexdigest()[:12] - 避免用
json.dumps(data, sort_keys=True)后哈希——浮点数精度、NaN序列化行为不一致,容易导致相同内容算出不同 ETag - 如果必须基于最终响应体,可在
after_request中读取response.get_data()(注意:这会禁用流式响应,且增加内存开销)
手动实现 ETag 校验并返回 304
Flask 没有内置 check_etag() 这类辅助函数,你得显式读请求头、比对、中断响应流程。
- 从
request.headers.get('If-None-Match')获取客户端携带的 ETag - 注意:HTTP 规范允许带
W/前缀(弱 ETag),但 Flask 场景下建议统一用强 ETag(无前缀),避免语义混淆 - 比对时用
==即可,不用str.strip('"')——现代浏览器发送的值已不含外层引号(RFC 7232 明确要求服务器忽略引号) - 校验通过就直接
return '', 304,不要调用make_response或设Content-Type,否则可能触发双重响应头
示例片段:
@app.route('/api/report')
def get_report():
user_id = request.args.get('user_id')
report_date = request.args.get('date')
etag = hashlib.sha256(f"{user_id}:{report_date}".encode()).hexdigest()[:12]
if request.headers.get('If-None-Match') == etag:
return '', 304
data = fetch_report(user_id, report_date)
resp = jsonify(data)
resp.headers['ETag'] = etag
resp.cache_control.max_age = 300
return resp
和 Flask-Caching 等扩展一起用时的冲突点
Flask-Caching 的 @cache.cached() 会绕过视图函数,直接返回缓存响应——这意味着你的 ETag 生成逻辑根本不会执行,ETag 值可能滞留在旧版本,导致后续 304 判断永远失败。
- 要么禁用该路由的自动缓存:
@cache.cached(timeout=0, unless=lambda: True) - 要么把 ETag 计算逻辑提到缓存键(
key_prefix)里,确保缓存命中时 ETag 仍准确 - 更稳妥的做法:不用 Flask-Caching 做响应级缓存,改用 Redis 存储预计算的 ETag + 数据体,由视图自己查、比、返
- 注意:
Response对象一旦被缓存,其headers也固化了,后续修改ETag字段无效
ETag 的核心不是“让响应更快”,而是“让变更立刻可见”——这点在管理后台、实时报表等场景里,比省那几十毫秒更重要。漏掉校验逻辑,或者用错哈希源,缓存就会变成数据不一致的温床。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











