etag是服务端对响应内容生成的唯一字节级摘要标识,python框架如flask默认不自动生成或校验,必须手动计算(如用响应体哈希)并比对if-none-match头,否则协商缓存失效导致返回过期数据。

ETag 是什么,为什么 Python 服务端要自己生成它
ETag 不是客户端传来的“魔法值”,而是服务端对响应内容做轻量摘要后生成的唯一标识。Python Web 框架(如 Flask、FastAPI)默认不自动计算 ETag,除非你显式调用 make_response 或设置 response.headers['ETag']。忽略这一点,直接依赖中间件或反向代理(如 Nginx)生成 ETag,会导致协商失效——因为 Nginx 看不到你动态拼接的 JSON 字段或运行时注入的字段,算出的哈希和实际响应体不一致。
常见错误现象:304 Not Modified 响应被返回,但前端拿到的是旧数据;或者明明内容变了,却始终返回 304。
实操建议:
- 优先用内容哈希(如
hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()),而非时间戳或数据库版本号——后者无法保证响应体字节级一致 - 避免在 ETag 中包含非确定性字段(如
datetime.now()、随机 UUID、未排序的 dict 键) - 如果响应含用户个性化字段(如
"last_login_ip"),ETag 必须包含这些字段的哈希,否则不同用户会共享缓存
Flask 中手动实现 If-None-Match 协商逻辑
Flask 不提供开箱即用的 ETag 自动协商,必须自己读 request.headers.get('If-None-Match') 并比对。
实操建议:
- 先生成响应体(如
jsonify(data)),再计算其 ETag;不要反过来先算 ETag 再构造响应,容易漏掉 header 注入或编码差异 - 注意 Flask 的
jsonify默认加Content-Type: application/json,但 ETag 应只基于响应体字节,不含 header - 比对时用
if request_etag == computed_etag:,别用in或模糊匹配——ETag 可能带W/"..."弱校验前缀,需先剥离 - 若匹配成功,直接返回空响应 +
status=304,**不要**再调用jsonify或写 body
示例关键片段:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
data = {"user_id": 123, "updated_at": "2024-06-01"}
body = json.dumps(data, sort_keys=True).encode()
etag = hashlib.md5(body).hexdigest()
request_etag = request.headers.get("If-None-Match", "").strip('"')
if request_etag == etag:
return "", 304
else:
resp = jsonify(data)
resp.headers["ETag"] = f'"{etag}"'
return resp
FastAPI 中用 Response 和 HTTPException 绕过自动 JSON 封装
FastAPI 默认对 return dict 自动转 JSON 并设 Content-Type,但这样你拿不到原始字节流来算 ETag。强行在依赖中预计算又破坏了响应构造流程。
实操建议:
- 用
Response(content=..., media_type="application/json")手动控制输出,确保content是 bytes 类型(便于哈希) - 不要用
JSONResponse,它内部会重新 encode,导致两次序列化,ETag 失效 - 用
HTTPException(status_code=304)返回 304,但注意它会清空 body —— 这正是你需要的 - ETag 值建议用双引号包裹(
f'"{etag}"'),符合 RFC 规范,避免某些 CDN 拒绝弱校验格式
示例关键片段:
data = {"count": 42}
body = json.dumps(data, sort_keys=True).encode()
etag = hashlib.sha256(body).hexdigest()
if request.headers.get("If-None-Match") == f'"{etag}"':
raise HTTPException(status_code=304)
return Response(
content=body,
media_type="application/json",
headers={"ETag": f'"{etag}"'}
)
生产环境绕不开的细节:gzip、字符编码与 CDN 兼容性
本地测试正常,上线后 ETag 失效?大概率是反向代理或 CDN 对响应做了 gzip 压缩或编码转换,导致客户端收到的字节 ≠ 服务端计算 ETag 时的字节。
实操建议:
- 在计算 ETag 前,确认响应是否会被压缩:若 Nginx 启用了
gzip on,且未配置gzip_vary on,则 ETag 必须基于压缩后字节(不现实),更稳妥的做法是禁用对 API 路径的 gzip(gzip_types application/json;删掉) - 统一用 UTF-8 编码 dump JSON,并显式指定
ensure_ascii=False,避免不同 Python 版本对中文处理不一致 - CDN(如 Cloudflare)可能忽略你设的 ETag,改用自己的策略;此时需在 CDN 后台开启 “Respect Origin ETag” 或类似开关
- 调试时用
curl -v -H "If-None-Match: \"xxx\"" https://api.example.com/xxx直连服务端,绕过 CDN 和 Nginx,定位问题环节
最易被忽略的一点:ETag 值里不能出现反斜杠 \ 或控制字符,hashlib 输出安全,但若手拼字符串(比如加前缀),务必用 hex 或 base64 编码。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










