
为什么 httputil.ReverseProxy 直接用会丢请求头?
因为默认不透传所有请求头,ReverseProxy 内部做了白名单过滤,比如 Connection、Keep-Alive、Proxy-Authenticate 等会被自动删掉——这是为防止代理链路污染,但多数业务场景下你其实需要它们。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 必须重写
Director函数,并在其中手动补全关键头:req.Header.Set("X-Forwarded-For", req.RemoteAddr)、req.Header.Set("X-Forwarded-Proto", "https")(若走 TLS) - 用
proxy.Transport设置自定义http.RoundTripper,避免默认的 30 秒超时和连接复用问题 - 别依赖
req.Header.Clone(),它不深拷贝底层 map,直接改req.Header即可
Director 函数里改 req.URL 的常见翻车点
改错路径会导致 404 或循环代理,尤其当后端是带路径前缀的服务(如 /api/v1/)时,req.URL.Path 必须显式重写,不能只靠 URL.Host 切换。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 先调用
u := url.URL{Scheme: "http", Host: "backend:8080"}构造新 URL,再用u.Path = strings.TrimPrefix(req.URL.Path, "/proxy")剥离代理路径 - 如果后端要求保留原始 query,记得执行
u.RawQuery = req.URL.RawQuery,否则参数丢失 - 不要在
Director里做阻塞操作(如 DB 查询、HTTP 调用),它在请求处理主 goroutine 中同步执行
如何让 ReverseProxy 正确转发 WebSocket?
原生 ReverseProxy 不支持 WebSocket 升级协商,直接代理会卡在 101 Switching Protocols 之后断连,因为 Upgrade 和 Connection 头被默认过滤了。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在
Director后、proxy.ServeHTTP前,加一段头透传逻辑:req.Header.Set("Connection", req.Header.Get("Connection"))、req.Header.Set("Upgrade", req.Header.Get("Upgrade")) - 确保
Transport的DisableKeepAlives设为false,否则长连接被主动断开 - 后端服务必须明确支持 WebSocket,且反向代理中间不能有不识别 Upgrade 的 LB(如某些云厂商的 HTTP 模式 SLB)
超时控制和错误响应怎么不暴露内部细节?
默认情况下,ReverseProxy 遇到后端超时或拒绝连接,会返回 502 Bad Gateway 并附带 Go 的 stack trace(开发环境)或空响应(生产),既不友好也不安全。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
proxy.ErrorHandler拦截错误:proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { http.Error(rw, "Service unavailable", http.StatusServiceUnavailable) } - 给
Transport显式设超时:&http.Transport{ResponseHeaderTimeout: 10 * time.Second, DialContext: dialer.DialContext} - 别把
err.Error()直接写进响应体,攻击者可能通过报错信息探测后端拓扑
最麻烦的是头透传和错误包装这两块,改一处常牵出三处隐性依赖,上线前一定拿真实客户端压测一遍 Upgrade 和带 Cookie 的 POST 场景。










