直接用 httputil.NewSingleHostReverseProxy 即可快速启动反向代理,但必须先 url.Parse 解析目标 URL,显式设置 Director 重写 req.URL.Scheme、req.URL.Host 和 req.Host,手动透传 X-Forwarded-For 等头,并通过 ModifyResponse 修改响应头,否则易出现 502、IP 丢失或路由错误。

直接用 go 命令行就能跑起来,不需要额外框架或第三方库——标准库 net/http/httputil 里自带的 NewSingleHostReverseProxy 就够用了。但别急着复制粘贴,几个关键点没处理好,代理会返回 502、丢 Header、透传失败,甚至把客户端 IP 搞成 127.0.0.1。
如何正确初始化 NewSingleHostReverseProxy
必须传入一个合法的 *url.URL,不能只写字符串或拼接 Host。常见错误是直接传 "http://localhost:8080" 而不解析:
❌ 错误写法:httputil.NewSingleHostReverseProxy("http://localhost:8080")(编译不过)
✅ 正确写法:先 url.Parse,再传指针:
target, _ := url.Parse("http://127.0.0.1:8080")
proxy := httputil.NewSingleHostReverseProxy(target)
注意:url.Parse 返回的是 *url.URL,且 Scheme 和 Host 字段必须非空,否则 Director 内部会 panic。
为什么请求被代理后丢失了 X-Forwarded-For 和真实 IP
NewSingleHostReverseProxy 默认会重写 X-Forwarded-For,但前提是原始请求里没有该 Header;如果客户端或上游 Nginx 已带了伪造的 X-Forwarded-For,它会直接追加,导致 IP 链混乱。
立即学习“go语言免费学习笔记(深入)”;
解决方法是显式覆盖 Director,并手动清理不可信 Header:
- 在
Director函数里删掉所有来自客户端的X-Forwarded-*头(防止 IP 欺骗) - 用
req.RemoteAddr提取真实客户端 IP(注意要切掉端口) - 手动设置
X-Forwarded-For为干净 IP
示例片段:
proxy.Director = func(req *http.Request) {
// 清理不可信头
req.Header.Del("X-Forwarded-For")
req.Header.Del("X-Real-Ip")
// 设置可信来源 IP
clientIP := strings.Split(req.RemoteAddr, ":")[0]
if clientIP == "::1" { clientIP = "127.0.0.1" }
req.Header.Set("X-Forwarded-For", clientIP)
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
}
ServeHTTP 必须绑定到具体 Handler,不能直接传 nil mux
常见错误是写 http.ListenAndServe(":8082", proxy) —— 这看似简洁,但 ReverseProxy 是 http.Handler,不是 http.Server 的完整 handler 实现;它不会自动处理 OPTIONS、HEAD 或路径匹配逻辑,遇到根路径以外的路由(比如 /api/v1/users)可能 404。
务必用 http.HandleFunc 显式挂载:
- 用
http.HandleFunc("/", proxy.ServeHTTP)可以代理全部路径 - 若只想代理特定前缀(如
/backend/),需在Director中重写req.URL.Path,并确保前端路径截断逻辑一致 - 避免用
http.ListenAndServe(":8082", nil),它会 fallback 到默认http.DefaultServeMux,而你没注册任何路由 → 404
启动代码应为:
http.HandleFunc("/", proxy.ServeHTTP)
log.Fatal(http.ListenAndServe(":8082", nil))
修改响应或注入 Header 必须用 ModifyResponse
想在返回给客户端前加个 X-Proxy-By: go,或统一改状态码、过滤敏感字段?不能在 Director 里操作响应,因为那时响应还没生成。
ModifyResponse 是唯一安全时机,且必须检查 resp 是否为 nil(超时或连接失败时会发生):
proxy.ModifyResponse = func(resp *http.Response) error {
if resp == nil {
return nil // 不 panic,跳过处理
}
resp.Header.Set("X-Proxy-By", "go")
return nil
}
注意:ModifyResponse 无法修改响应 body(除非用 httputil.DumpResponse 全量读取再重写),性能敏感场景慎用。
最易被忽略的一点:如果你的后端服务依赖 Host 头做路由(比如多租户 SaaS),NewSingleHostReverseProxy 默认不会改 req.Host,它仍保留客户端发来的 Host;必须在 Director 里显式赋值 req.Host = target.Host,否则后端可能 404 或路由错位。


















