iris默认不自动添加etag和cache-control,因其静态服务基于go标准库http.servefile,而标准库刻意避免隐式缓存头以保障调试与部署一致性。

Iris 默认不自动加 ETag,也不控制 Cache-Control;它把缓存头完全交给你手动设——这既是自由,也是容易出错的根源。
为什么 Iris 的静态文件服务不带 ETag 和强缓存?
Iris 的 app.StaticWeb、app.StaticServe 或 app.Favicon 底层用的是标准库的 http.ServeFile 或简单包装,而 Go 标准库明确不为静态文件自动注入 ETag、Last-Modified 或 Cache-Control。这不是 Iris 的缺陷,是设计选择:避免隐式行为干扰调试或部署一致性。
常见错误现象:
– 访问 /static/app.js 返回 200,响应头里没有 ETag;
– 加 If-None-Match 请求头后仍返回完整 body,不是 304;
– 浏览器反复下载同一份 JS,Lighthouse 报“未启用有效缓存”。
- 别指望
app.StaticServe("/static", "./dist")自动给你缓存逻辑 - 别在路由里用
ctx.ServeFile()后补ctx.Header("Cache-Control", ...)—— 它不校验If-None-Match,304 流程根本不会触发 - 开发时关掉浏览器缓存(DevTools → Network → Disable cache)能帮你快速验证真实行为
用 http.FileServer + 中间件替代 StaticServe
这是最轻量、可控、且兼容 Iris 路由体系的做法:绕过 Iris 的静态封装,直接用标准库带缓存能力的机制,再挂到 Iris 的 app.Any 或子路由器上。
核心是让 http.FileServer 处理文件读取和 ETag/Last-Modified 生成,你只负责注入 Cache-Control:
fs := http.FileServer(http.Dir("./dist"))
cacheHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
fs.ServeHTTP(w, r)
})
app.Any("/static/{filepath:path}", iris.FromStd(cacheHandler))
-
immutable只能用于内容哈希命名的资源(如main.a1b2c3.js),否则更新后用户永远拿不到新版本 - 如果资源没哈希化,改用
public, max-age=3600,保留协商缓存能力 -
iris.FromStd是关键桥接函数,把标准http.Handler转成 Iris 可用的iris.Handler - 别用
app.UseRouter或app.WrapRouter去包整个路由树——太重,也容易干扰其他中间件顺序
需要精确 ETag 控制?用 http.ServeContent 手写 handler
当你要弱校验(W/"xxx")、基于内容哈希而非 mtime 生成 ETag,或想复用构建时预计算的哈希值时,http.ServeContent 是唯一可靠选择。它会自动处理 If-None-Match 和 If-Modified-Since,并正确返回 304。
示例逻辑(放在 Iris 路由中):
app.Get("/assets/{file:path}", func(ctx iris.Context) {
path := "./dist/" + ctx.Params().Get("file")
f, err := os.Open(path)
if err != nil {
ctx.StatusCode(404)
return
}
defer f.Close()
fi, _ := f.Stat()
// 构建弱 ETag:避免 gzip 差异导致校验失败
etag := fmt.Sprintf(`W/"%x"`, md5.Sum([]byte(fi.Name()+strconv.FormatInt(fi.Size(), 10))))
ctx.Header("ETag", etag)
ctx.Header("Cache-Control", "public, max-age=31536000")
http.ServeContent(ctx.ResponseWriter(), ctx.Request(), fi.Name(), fi.ModTime(), f)
})
- 每次请求都
os.Open+Stat+md5.Sum?性能扛不住——生产环境必须预计算并缓存 ETag,比如启动时扫描./dist目录生成 map[string]string 映射表 -
http.ServeContent内部会检查ETag和If-None-Match,匹配就写 304 并 return,不会继续读文件 - 别漏掉引号:
"W/\"xxx\""是合法格式,"W/xxx"会被浏览器忽略 - 如果你用 Webpack/Vite,直接开启
assetPrefix+filename: "[name].[contenthash].js",然后跳过运行时 ETag 计算,靠immutable+ 文件名哈希就够了
真正难的不是加一行 Cache-Control,而是判断这个资源到底该不该缓存、缓多久、靠什么验证。文件名没哈希却配 immutable,或者 API 接口误加 public,比不加缓存更危险——它会让错误内容在 CDN 和浏览器里顽固驻留数天。动手前,先看清楚你发出去的每个响应,到底是谁在用、变不变、变了有没有通知机制。











