最可靠的是用c.Request.Host,但它不含协议且可能带端口;不能只靠c.Request.URL.Host,因其同源Host头、不包含协议,且在反代后无法反映真实Scheme,需结合X-Forwarded-Proto和X-Forwarded-Host等可信代理头拼接完整域名。

直接用 c.Request.Host 最可靠,但要注意它不含协议,且可能带端口;需要拼完整域名时得结合反向代理头或 Scheme 判断。
为什么不能只靠 c.Request.URL.Host?
因为 c.Request.URL.Host 和 c.Request.Host 本质一样,都来自原始 HTTP 请求的 Host 头(如 example.com:8080),它不包含协议(http:// 或 https://),也不反映真实部署环境(比如 Nginx 反代后,Host 是用户访问的域名,但 Scheme 在请求体里根本不存在)。
- 前端通过 HTTPS 访问,但 Gin 收到的
Request.URL.Scheme永远是空字符串 —— HTTP 标准不把 Scheme 当请求头传 - 如果服务跑在 8080 端口且没被反代,
c.Request.Host就是example.com:8080,但你通常不希望模板里渲染出端口 - 用
c.Request.URL.Hostname()能去掉端口,但丢掉了用户实际访问的端口信息(比如 80/443 隐式存在,其他端口需显式保留)
如何在模板里安全拼出完整域名(含协议)?
生产环境几乎总是有反向代理(Nginx / Cloudflare / ALB),所以应优先信任 X-Forwarded-Proto 和 X-Forwarded-Host 这类代理头,再 fallback 到 Host。
- 在 handler 中提前组装好完整 URL 字符串,传给模板:
domain := "https://" + c.GetHeader("X-Forwarded-Host")(若存在)
否则退化为"https://" + c.Request.Hostname(),再根据X-Forwarded-Proto动态替换协议 - 务必校验
X-Forwarded-*头是否可信 —— 如果你的 Gin 前没有可信代理,直接读这些头会被伪造 - 不要在模板里做逻辑判断,Gin 模板不支持复杂条件拼接协议+host;所有判断必须在 handler 层完成
c.Request.Host 和 c.Request.URL.Hostname() 的实际差异
假设用户访问 https://api.example.com:3000/v1/users:
-
c.Request.Host→api.example.com:3000(原始 Host 头) -
c.Request.URL.Hostname()→api.example.com(去掉端口,但丢失了非标准端口信息) -
c.Request.URL.Port()→"3000"(注意:返回空字符串表示无显式端口,不是 80 或 443) -
c.GetHeader("X-Forwarded-Host")→api.example.com(若 Nginx 配了proxy_set_header X-Forwarded-Host $host;)
真正要渲染「用户当前访问的完整域名」,最简方案就是:domain := c.GetHeader("X-Forwarded-Host"),fallback 到 c.Request.Hostname(),协议则统一按 c.GetHeader("X-Forwarded-Proto") 或硬编码(开发环境)处理。
最容易被忽略的是:没配反代时硬读 X-Forwarded-* 头会导致空值或被篡改;而只依赖 Host 又无法还原协议 —— 这个边界必须在 handler 层明确区分,不能甩给模板。











