最直接的方式是调用c.Request().Header.Get("User-Agent");需避免使用已过期的c.Request().UserAgent()字段,反向代理场景下应结合X-Original-User-Agent等可信头及IP校验,并推荐用uap-go等库解析UA字符串。

Context.Request.Header.Get("User-Agent") 是最直接的方式
Echo 的 echo.Context 本身不提供 GetUserAgent() 这类封装方法,必须从底层 *http.Request 的 Header 中手动取。注意大小写:HTTP 头字段名是大小写不敏感的,但 Go 的 Header.Get() 内部做了标准化,"User-Agent"、"user-agent" 或 "USER-AGENT" 都能命中,不过按惯例统一用首字母大写的驼峰形式更稳妥。
常见错误是误调用 c.Request().UserAgent() —— 这个方法确实存在,但它返回的是 Request.UserAgent 字段,而该字段**只在第一次调用时从 Header 初始化,之后不会自动同步更新**。如果中间有中间件修改了 Header(比如反向代理透传了 X-Original-User-Agent 并覆盖了 Header),用 UserAgent() 就会拿到过期值。
所以始终优先用:
c.Request().Header.Get("User-Agent")
反向代理场景下 UserAgent 可能被覆盖或伪造
如果你的服务前面有 Nginx、Cloudflare 或其他代理,原始 UA 往往被写进 X-Forwarded-For 类字段,而 User-Agent Header 可能被代理重写(比如 Nginx 默认不透传,需显式配置 proxy_set_header User-Agent $http_user_agent;)。这时光读 Header 不够,得结合可信代理链判断。
实操建议:
- 确认上游代理是否透传了
User-Agent;没透传就只能依赖X-Original-User-Agent等自定义头(需双方约定) - 若启用了可信 IP 列表(如
c.RealIP()依赖的X-Forwarded-For),可配合检查X-Real-IP或X-Forwarded-For是否来自可信段,再决定是否信任该请求携带的User-Agent - 不要无条件信任
User-Agent做安全决策(比如绕过风控),它极易被客户端篡改
封装成 Context 方法需要自己扩展
Echo 不内置 UA 获取的快捷方法,但你可以给 echo.Context 添加一个方法(通过类型别名 + 方法绑定),避免每次重复写长路径:
type CustomContext struct {
echo.Context
}
func (c *CustomContext) GetUserAgent() string {
return c.Request().Header.Get("User-Agent")
}
然后在 Handler 中类型断言使用:
func handler(c echo.Context) error {
cc := &CustomContext{Context: c}
ua := cc.GetUserAgent()
// ...
}
注意:不能直接给接口类型 echo.Context 添加方法,必须基于结构体包装;也不建议用全局函数封装(比如 GetUA(c)),那样丢失了面向对象的可读性和 IDE 支持。
移动端 / 爬虫识别建议用第三方库解析
单纯读到字符串 User-Agent 没法直接判断设备类型或是否为爬虫。比如 "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)..." 需要解析才能知道是 iOS 17.5 的 iPhone。
推荐轻量方案:
-
uap-go:专注 UA 解析,支持设备、OS、浏览器三级分类,性能好,无依赖 -
gologin:更重一点,但对爬虫 UA(如 Selenium、Playwright)识别率高 - 避免手写正则匹配——UA 格式碎片化严重,维护成本高,且容易漏判新型 UA
解析结果应缓存(比如用 sync.Map 缓存已解析过的 UA 字符串),因为同一 UA 在短时间内高频出现概率很高,重复解析浪费 CPU。
实际项目里最容易被忽略的是代理链中的 Header 覆盖行为,以及把 UserAgent() 当作实时值来用。这两个点一旦出错,日志里的设备统计或 A/B 测试分组就会系统性偏差。











