ETag是资源内容强相关的唯一标识符,应使用md5或sha256等摘要算法生成;禁用时间戳、ID、UUID等非内容相关值;Go需手动处理If-None-Match校验与ETag响应头设置。

ETag 是什么,为什么不能直接用时间戳
ETag 本质是资源的唯一标识符,服务端生成、客户端携带,用于判断缓存是否“真正一致”。它不是时间戳,也不是版本号字符串——哪怕资源内容没变,但用了 time.Now().Unix() 当 ETag,每次响应都会变,缓存完全失效。
- 理想 ETag 应该是内容强相关:比如
md5(content)或sha256(content)的十六进制摘要 - 若资源大或计算成本高,可用弱 ETag(前缀
W/"..."),例如基于修改时间和大小组合:W/"12345-67890" - 避免用数据库自增 ID、随机数、UUID——它们和内容无关,无法保证语义一致性
Go 标准库里怎么生成和校验 ETag
标准库没有自动 ETag 支持,得自己在 handler 里手动处理 If-None-Match 请求头和 ETag 响应头。核心逻辑就三步:算 ETag → 检查请求头 → 决定返回 304 还是 200。
- 生成 ETag 推荐用
http.ServeContent配合http.ServeFile不行,它不支持自定义 ETag;得自己读内容再写响应 - 校验时注意:
If-None-Match可能是多个值(逗号分隔),也可能带W/前缀,需做标准化比对 - Go 的
http.ResponseWriter不会自动设置Cache-Control: no-cache,你要显式加,否则浏览器可能跳过验证
// 示例:简单内容 ETag 校验
func handleWithETag(w http.ResponseWriter, r *http.Request) {
body := []byte("hello world")
etag := fmt.Sprintf(`"%x"`, md5.Sum(body)) // 强 ETag
if match := r.Header.Get("If-None-Match"); match != "" {
if strings.Contains(match, etag) || strings.Contains(match, "W/"+etag) {
w.WriteHeader(http.StatusNotModified)
return
}
}
w.Header().Set("ETag", etag)
w.Header().Set("Cache-Control", "no-cache")
w.Write(body)
}
什么时候该用弱 ETag 而不是强 ETag
强 ETag 要求字节级一致,适合静态文件、JSON API 等;弱 ETag 允许语义等价(比如空格、换行差异),适合模板渲染结果、HTML 页面等易受非关键变更影响的场景。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 弱 ETag 必须以
W/"..."开头,且服务端必须确保:相同弱 ETag ⇒ 内容对用户“看起来一样” - Go 里生成弱 ETag 常见做法是拼接修改时间 + 文件大小:
W/"%d-%d",但要注意:如果内容变了但大小和时间没变(比如替换了同长度字符串),就会出错 - 浏览器对弱 ETag 的处理更宽松,
If-None-Match匹配时也会接受弱比较,但服务端仍要自己实现逻辑,标准库不介入
常见踩坑:304 响应体为空但 Header 没清干净
返回 http.StatusNotModified 时,你不能调用 w.Write(...),但很多人忘了清理之前可能已写的 Header —— 比如之前设了 Content-Type 或 Content-Length,这些字段在 304 响应中是禁止出现的,Chrome 会直接拒绝缓存。
- Go 的
http.ResponseWriter在写入状态码后,Header 修改会被忽略;但如果你在判断前就写了 Header,就得手动清除:w.Header().Del("Content-Type") - 更安全的做法是:所有 Header 设置放在校验之后、写 body 之前;或者统一用中间件封装 ETag 逻辑,避免业务 handler 直接触碰 Header
- 本地调试时用
curl -v看响应头,重点检查是否含Content-Length或Content-Type—— 有就是错
ETag 的难点不在生成,而在一致性边界:你得明确“什么算同一个资源”,然后让 ETag 和这个定义严格对齐。一旦定义模糊,比如混用数据库字段和内容哈希,缓存就会间歇性失效或错误命中。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










