gin 不支持原生多主机路由,需用中间件读取 host 头并重写 url 路径或通过 c.set("domain") 注入上下文实现多域名分发,严禁使用不存在的 router.host() 或 group("xxx.com")。

Gin 本身不支持 router.Host() 这类原生多主机路由,硬写 r.Group("admin.example.com") 或在路径里塞域名(如 /admin.example.com/users)完全无效——Host 头根本不会参与 Gin 的路由匹配过程。
用中间件提前读 Host 并重写 c.Request.URL.Path
这是最轻量、最可控的做法:在所有路由注册前插入一个全局中间件,根据 c.Request.Host 值动态前置路径前缀,把域名语义“翻译”成 Gin 能识别的路径结构。
- 必须放在
r.Use(...)中,且要在r.GET等注册之前调用 - 注意清理端口:
strings.TrimRight(c.Request.Host, ":80")或更稳妥地用c.Request.URL.Hostname()(Go 1.25+) - 若原始请求是
/api/profile,重写为/admin/api/profile,那你的路由就必须注册在adminGroup := r.Group("/admin")下,而不是r.Group("/api") - 反向代理场景下优先读
c.GetHeader("X-Forwarded-Host"),否则可能拿到的是localhost:8080
用 c.Set() 注入 domain 上下文再分支处理
比起重写 URL,这种方式更干净,适合逻辑差异大、共享部分路由但 handler 行为需定制的场景。中间件只负责解析并存 domain,后续 handler 自行判断。
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
- 中间件中写:
c.Set("domain", "user.example.com") - handler 里取:
domain, _ := c.Get("domain"),再switch domain分支 - 避免在中间件里调用
c.Next()后再改上下文键——Gin 的路由已锁定,此时设值对当前请求有效,但无法影响路由选择 - 注意并发安全:
c.Set()是 per-request 的,无需加锁
别踩 router.Host() 和 Group("xxx.com") 这两个坑
这两个写法在 Gin 官方文档和源码中都不存在,属于常见误传。一旦写了,既不会报错,也不会生效,请求会默默 fallback 到默认路由或 404。
-
router.Host("api.example.com")→ 编译失败(无此方法)或 IDE 提示未定义 -
r.Group("admin.example.com").GET("/users", ...)→ Gin 把它当普通路径字面量,实际注册的是GET /admin.example.com/users,跟 Host 头毫无关系 - 试图用
c.Request.Host在router.NoRoute()里做兜底跳转?晚了——NoRoute 已是路由失败后的最后一步,无法逆转匹配结果
真正麻烦的不是怎么写,而是 Host 值的可靠性:Nginx 反代后没配 proxy_set_header Host $host;,或者客户端发错 Host 头,都会让整个分发逻辑失效。建议在中间件开头加日志打点,把 c.Request.Host 和 c.GetHeader("X-Forwarded-Host") 都打印出来,上线前用 curl -H "Host: user.example.com" http://localhost:8080/ 手动验证。










