go 的 net/http 需手动设置 server-timing 响应头,用 w.header().set() 写入符合 w3c 规范的逗号分隔字段(如 "db;dur=5.1,cache;dur=2.7"),时间单位为毫秒浮点数字符串,chrome 仅识别前三个合法字段;nginx 默认过滤该头,需移除 proxy_hide_header server-timing 配置。

Go 的 net/http 怎么加 Server-Timing 头
Go 标准库不自动支持 Server-Timing,必须手动写入响应头。它本质就是个普通 HTTP 响应头,格式自由但需遵守规范:逗号分隔的键值对,可带描述和数字度量。
实操建议:
- 在
http.HandlerFunc里、w.WriteHeader()或w.Write()之前调用w.Header().Set("Server-Timing", "...") - 不要用
Add()—— 多次调用会拼接出非法格式(如重复字段),Set()覆盖更安全 - 字段名不能含空格或特殊符号;推荐用短小英文标识,比如
db、cache、render - 时间单位必须是毫秒,且为浮点数字符串,例如
db;dur=12.34,不是12340000纳秒
Server-Timing 字段怎么组织才被 Chrome DevTools 识别
Chrome 只解析符合 W3C 规范 的字段格式,且只显示前 3 个(按顺序),超出部分静默丢弃。字段名重复会导致覆盖,不是累加。
常见错误现象:
立即学习“go语言免费学习笔记(深入)”;
- 写了
Server-Timing: db;dur=5.1, cache;dur=2.7, render;dur=8.9, auth;dur=1.2,但 DevTools 只显示前三项 - 字段名含下划线(如
db_query)或大写字母(如DB),部分浏览器解析失败或忽略 - 用了
ms单位后缀(如dur=5.1ms)—— 规范明确要求无单位,只接受纯数字
推荐写法:w.Header().Set("Server-Timing", "db;dur=5.1,cache;dur=2.7,render;dur=8.9")
如何在 Gin / Echo 等框架中安全注入 Server-Timing
中间件是最佳位置,但要注意执行时机:必须在业务逻辑结束后、响应写出前收集耗时,不能在请求刚进来就写头。
使用场景:
- Gin:用
c.Next()后记录耗时,再写头;别在c.Abort()分支漏掉写头逻辑 - Echo:同理,在
next(c)后取c.Response().Status和耗时,避免 panic(比如未启动写入就访问Response().Writer) - 如果用了 gzip 中间件(如
gin-contrib/gzip),确保Server-Timing在 gzip 之前设置,否则可能被拦截或覆盖
示例(Gin 中间件片段):
w := c.Writer
c.Next()
duration := time.Since(start).Seconds() * 1000
w.Header().Set("Server-Timing", fmt.Sprintf("handler;dur=%.2f", duration))为什么本地测出来有头,线上 Nginx 却没了
Nginx 默认不透传自定义响应头,Server-Timing 不在白名单里,会被直接剥离。
关键配置点:
- 在 Nginx 的
location或server块中加:proxy_pass_request_headers on;(通常默认开启,但检查一下) - 必须显式允许该头:
proxy_hide_header Server-Timing;这行要删掉或注释掉 - 如果用了
underscores_in_headers on;,注意Server-Timing含连字符,不受影响;但若你误写成server_timing,Nginx 会直接拒绝(除非配了underscores_in_headers on且关掉ignore_invalid_headers)
验证方式:curl -I http://your-domain/ | grep Server-Timing —— 必须在 Nginx 日志后端直连 Go 服务也跑一遍,比对结果差在哪一层。
容易被忽略的是:Kubernetes Ingress、CDN(如 Cloudflare)、甚至某些 TLS 终止代理,都可能过滤或重写这个头。别假设它能一路透传到底。











