应优先使用 httputil.newsinglehostreverseproxy 实现安全转发,它自动处理头透传、连接复用等;手动 req.write() 仅适用于需深度控制请求体的场景;https 代理必须 hijack 并双向 io.copy 原始字节流,且严格管理连接生命周期防泄漏。

直接用 http.ServeHTTP 转发原始请求会出错,因为后端服务期望的是已解析、上下文完整的入站请求,而代理拿到的 *http.Request 里 Host、URL.Path、hop-by-hop 头(如 Connection、Transfer-Encoding)往往未清理,导致 404、空响应或 502。
用 httputil.NewSingleHostReverseProxy 构造安全转发器
这是最轻量、最不易出错的起点。它自动处理请求头透传、响应头复制、连接复用、重定向跟随,并规避了手动拼接 URL 或忽略 Content-Length 等常见陷阱。
- 必须通过
Director函数显式重写req.URL和req.Host,例如:req.URL.Scheme = "http"; req.URL.Host = "127.0.0.1:8080" -
Director中需同步更新req.URL.RawPath(若原始请求含编码路径),否则可能触发 400 - 它默认过滤 hop-by-hop 头,但若你额外添加了自定义头(如
X-Forwarded-For),需在Director里手动设置 - 不支持 HTTPS 后端直连;若目标是
https://,需配置http.Transport并启用 TLS
手动调用 req.Write() 的适用场景与风险
当你需要完全控制请求写出过程(比如做中间人修改 Body、注入二进制 payload、或对接非标准 HTTP 服务),就得绕过 httputil,直接使用 req.Write()。
-
req.Write()会完整写出请求行、所有 Header 和未消费的req.Body,但前提是req.Body仍可读——http.ReadRequest已消费 Body 时,必须提前用io.NopCloser或bytes.NewReader重建 - 修改 Host 头必须设
req.Host字段,而非只改req.Header.Set("Host", ...),否则Write()可能忽略 - 对 HTTPS 目标,必须用
tls.Dial建连,并确保 SNI 名称与req.Host一致,否则证书校验失败 - 不处理 HTTP/2;若下游是 HTTP/2 服务,此方式会降级为 HTTP/1.1
CONNECT 方法必须 hijack,不能走常规 HTTP 流程
HTTPS 代理的核心是隧道,不是 HTTP 转发。浏览器发起 CONNECT google.com:443 后,服务器不能返回 HTTP 响应体,而要立即升级连接并双向拷贝原始字节流。
- 必须用类型断言获取底层连接:
hijacker, ok := w.(http.Hijacker); if !ok { return } - 调用
hijacker.Hijack()后,原http.ResponseWriter失效,不能再写任何 HTTP 响应 - 建立到目标的
net.Conn后,启动两个io.Copy协程:一个从客户端拷到远端,另一个反向;任一方向 EOF 都应关闭双方连接 - 务必禁用超时:
clientConn.SetDeadline(time.Time{}),否则io.Copy可能被中断,导致浏览器卡死或 TLS 握手失败
真正容易被忽略的不是代码怎么写,而是连接生命周期管理——每个 io.Copy 都得配对 defer conn.Close(),每个 Hijack() 后的连接都必须显式关闭,漏掉一处就会泄漏连接,压测时迅速耗尽文件描述符。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











