直接用 gin.Default() + httputil.NewSingleHostReverseProxy 能跑通基础转发,但必须配置 Director 重写 URL 和 Host 头、设置 FlushInterval、自定义 Transport 控制超时与连接复用,并复用代理实例,否则易出现 502、卡死或 302 跳转错误。

直接用 gin.Default() + httputil.NewSingleHostReverseProxy 就能跑通基础转发,但不处理 Host 头、路径重写或超时控制的话,一上线就 502 或连接卡死。
用 ReverseProxy 封装成 Gin 中间件
Go 标准库的 httputil.NewSingleHostReverseProxy 是核心,但它返回的是 http.Handler,不能直接塞进 Gin 的 router.GET。必须包一层适配器:
- 用
url.Parse("http://localhost:3000")构造目标地址,传给NewSingleHostReverseProxy - 设置
Director函数:改写c.Request.URL,把前缀(如/api/v1)去掉,否则后端收到带前缀的路径会 404 - 显式设置
Request.Header.Set("Host", ...),否则后端看到的 Host 是网关地址,可能触发证书校验失败或路由错乱 - 不要漏掉
FlushInterval:大响应体(如文件流)不设这个,客户端会等超时才收到数据
Director 里路径重写的常见错误
比如你想把 GET /api/v1/users 转发到 http://svc-users/users,但没动 c.Request.URL.Path,后端实际收到的就是 /api/v1/users —— 它根本没这路由。
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
- 正确做法:在
Director里执行req.URL.Path = strings.TrimPrefix(req.URL.Path, "/api/v1") - 注意
strings.TrimPrefix不处理重复斜杠,如果前端发来//api/v1/,得额外清理 - 别用
req.URL.Path = "/users"这种硬编码,路径参数(如/api/v1/user/:id)会被吃掉 - 如果后端要求保留原始路径前缀(比如它自己做路由),那就别 trim,改用
req.URL.Path = "/v1" + req.URL.Path类似逻辑
超时和 Transport 配置不能依赖默认值
http.DefaultTransport 的默认行为对网关是灾难性的:DNS 缓存 0 秒(其实是永久缓存)、空闲连接复用无上限、单次请求超时 30 秒且不可调 —— 后端抖动时网关线程池瞬间被占满。
- 必须自定义
&http.Transport{},设IdleConnTimeout: 30 * time.Second和MaxIdleConnsPerHost: 100 - 代理实例的
Transport字段要指向这个新实例,不是改http.DefaultTransport -
Director之外,还要在ModifyResponse里检查resp.StatusCode >= 400并记录日志,否则失败转发静默吞掉错误 - 别在每次请求都新建
ReverseProxy实例,它不是线程安全的,应初始化一次复用
真正难的不是写通转发,而是当后端返回 302 重定向时,ReverseProxy 默认会把 Location 头原样透传——如果后端 Location 是 http://localhost:3000/login,客户端就跳去访问网关本机,而不是经过网关再转发。这个细节几乎没人提,但线上一出问题就是用户打不开页面。


















