gin 本身不支持原生子域名路由,subdomain() 已移除;必须通过反向代理(如 nginx)分流或在中间件中解析 host 头实现,group() 仅处理路径前缀,无法匹配子域名。

子域名路由必须用 Group() 还是 SubDomain()?
Gin 本身不支持原生子域名路由,SubDomain() 是旧版(v1.3 之前)的实验性方法,早已移除;现在唯一可靠的方式是靠反向代理(如 Nginx、Caddy)把不同子域名转发到不同端口或路径,再由 Gin 用普通路由或分组处理——或者手动解析 Host 头做分发。
直接在 Gin 中硬编码子域名匹配(比如 c.Request.Host == "api.example.com")可行但脆弱:HTTPS 下 Host 可能带端口(api.example.com:443),本地调试时又常是 localhost,容易漏判。
- 推荐做法:Nginx 把
api.example.com→ 转发到127.0.0.1:8081,admin.example.com→127.0.0.1:8082,每个端口起一个独立gin.Engine - 次选做法:所有子域名打到同一端口,在入口中间件里用
c.Request.Host提取子域名前缀,再调用c.Redirect()或c.AbortWithStatusJSON()分流 - 绝对避免:在每个 handler 里重复写
strings.HasPrefix(c.Request.Host, "api.")—— 逻辑分散、无法统一鉴权、中间件失效
如何用单个 Gin 实例区分 api.example.com 和 www.example.com?
靠中间件 + gin.RouterGroup 模拟“虚拟子域名路由”:先在全局中间件中提取子域名,存入 c.Set("subdomain", sub),再在路由注册时按需分组,但注意——Group() 只管路径前缀,不管 Host,所以必须配合中间件拦截。
示例结构:
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
func SubdomainMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
host := c.Request.Host
if i := strings.Index(host, "."); i > 0 {
sub := host[:i]
c.Set("subdomain", sub)
} else {
c.Set("subdomain", "www")
}
c.Next()
}
}
r := gin.New()
r.Use(SubdomainMiddleware)
api := r.Group("/api") // 注意:这里只是路径分组,不是子域名
api.Use(RequireSubdomain("api")) // 自定义中间件校验 c.MustGet("subdomain") == "api"
api.GET("/users", getUsers)
www := r.Group("/www")
www.Use(RequireSubdomain("www"))
www.GET("/home", getHome)
-
RequireSubdomain必须在Group().Use()中显式挂载,不会继承自根路由 - 这种写法本质是“路径 + Host 双重约束”,不是真正的子域名路由,但能复用 Gin 的分组组织能力
- 若需完全隔离(如 admin 子域名禁用所有 /api 路由),必须用独立进程或反向代理分流,否则无法阻止恶意请求绕过中间件直击路径
为什么 r.Any() 或 r.Handle() 不能解决子域名问题?
r.Any() 和 r.Handle() 只匹配 HTTP 方法和路径,完全忽略 Host 请求头。哪怕你注册了 r.Any("api.example.com/users", handler),Gin 会把它当字面路径处理,最终注册的是一条匹配 GET /api.example.com/users 的路由,而非子域名路由。
- 错误写法:
r.GET("admin.example.com/dashboard", handler)→ 实际监听的是GET /admin.example.com/dashboard,跟子域名无关 - 正确思路:子域名属于 HTTP/1.1 协议层的 Host 字段,路由框架只负责路径+方法,Host 判断必须交由中间件或前置代理完成
- 如果强行在 handler 里做 Host 校验,会导致 CORS、OPTIONS 预检失败(因为预检请求的 Host 也得通过校验),且所有中间件(如日志、JWT)都得重复判断
生产环境最稳的子域名方案是什么?
用 Nginx 做子域名终结:每个子域名单独 server 块,proxy_pass 到不同上游(甚至不同二进制),Gin 应用本身只暴露 localhost 端口,不处理任何 Host 相关逻辑。
例如:
server {
listen 443 ssl;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
}
}
server {
listen 443 ssl;
server_name admin.example.com;
location / {
proxy_pass http://127.0.0.1:8082;
proxy_set_header Host $host;
}
}
- 好处:SSL 终结、负载均衡、缓存、WAF 都可在 Nginx 层统一配置
- Gin 应用彻底无感子域名,专注业务路由和中间件,调试时改 hosts 就能模拟多子域名
- 风险点:别忘了在
proxy_set_header中透传真实 Host,否则c.Request.Host会变成127.0.0.1:8081,丢失原始子域名信息
子域名不是路径前缀,它属于网络层标识,硬要在 Gin 里“模拟”只会增加耦合和出错概率。真正要拆开的不是代码,是部署结构。










