必须重写Director,否则默认裸转发导致404/502/跳转错乱;需显式设置req.URL.Scheme、req.URL.Host、req.URL.Path,透传X-Real-IP,删除hop-by-hop头,并调优Transport连接池与超时。

httputil.NewSingleHostReverseProxy 必须重写 Director
不重写 Director 就等于裸转发,90% 的生产问题都从这里开始:后端收不到真实 Host 头、路径带网关前缀、X-Forwarded-For 缺失、重定向跳到 localhost。它默认只改 req.URL.Host,其余全留原样。
必须显式设置以下三项:
-
req.URL.Scheme = backendURL.Scheme(否则 HTTPS 后端可能收到 HTTP URL,触发 301) -
req.URL.Host = backendURL.Host(否则下游按 Host 做租户路由或 CORS 判断会失败) -
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/api/v1")(别用正则替换,/api/v1/user会被误伤成/user)
顺手补上:req.Header.Set("X-Real-IP", getClientIP(req)),其中 getClientIP 要从 X-Forwarded-For 最左非私有 IP 提取,不能直接信 req.RemoteAddr。
Transport 连接池和超时必须手动调优
用 http.DefaultTransport 是线上事故高发区:默认 MaxIdleConnsPerHost = 2,高并发下连接耗尽;DNS 缓存永久生效;空闲连接不释放;大文件上传容易 OOM。
立即学习“go语言免费学习笔记(深入)”;
关键字段要设全:
-
MaxIdleConns和MaxIdleConnsPerHost都设为100起步 -
IdleConnTimeout = 30 * time.Second(对齐下游 keep-alive timeout) -
Timeout = 5 * time.Second(比下游 P99 高 2 秒即可,太短误杀,太长拖垮并发) - 删掉
TLSClientConfig.InsecureSkipVerify: true—— 测试用的,上线必须删
别碰 DialContext 除非你真需要接管 DNS TTL;系统 resolver 在多数场景下够用,强行换 Go resolver 可能引入额外延迟。
路由层选 gin 而非 http.ServeMux 或 gorilla/mux
http.ServeMux 自动裁剪路径前缀,下游服务拿不到原始路径,生成的重定向 URL 错乱;gorilla/mux 中间件模型松散,一个 panic 就可能让整个网关挂掉;而 gin 的 c.AbortWithStatus() 能明确中断流程,中间件链可控,压测 QPS 高出 15%~20%。
典型用法:
- 用
r.Group("/api/v1/users")统一加鉴权/限流中间件 - 失败时调
c.AbortWithStatus(401),后续中间件不执行 -
c.Request.URL.Path和c.FullPath()能稳定拿到原始路径,适合前置分发 - 已有
net/httphandler?用gin.WrapF()包一层就行
别自己实现 path prefix match,router.Group("/api").Use() 自动剥离前缀,逻辑收口更安全。
ErrorHandler 和 ModifyResponse 不是可选项
不设 ErrorHandler,上游连接拒绝或超时会直接 panic,网关进程 crash;不设 ModifyResponse,就无法统一加 X-Powered-By、清理敏感 header 或改写状态码。
最小可用示例:
-
ErrorHandler:设Content-Type: application/json,返回标准错误体,前端才不会半夜打电话 -
ModifyResponse:删掉Server、X-Powered-By等暴露技术栈的 header - 记得在
Director里删 hop-by-hop header:req.Header.Del("Connection")、req.Header.Del("Keep-Alive")
最易被忽略的是 req.RequestURI = "" —— 不清这个,ReverseProxy 会拼错路径,尤其在带 query string 的请求里翻车。



















