直接用 net/http 反向代理易出 502 或连接复用失败,因其默认不透传 Host 头、不处理 HTTP/2 降级、不支持非 HTTP 协议;后端返回非标状态码会 panic,HTTP/1.1 keep-alive 与后端短连接混用时复用已关闭连接导致 EOF。

为什么直接用 net/http 做反向代理容易出 502 或连接复用失败
Go 标准库的 httputil.NewSingleHostReverseProxy 默认不透传原始 Host 头、不处理 HTTP/2 连接降级、也不支持非 HTTP 协议。一旦后端返回非标准状态码(比如 444 或自定义协议响应),ReverseProxy 会直接 panic 或静默丢包;更常见的是,当客户端用 HTTP/1.1 keep-alive 而后端是短连接时,ReverseProxy 会复用已关闭的底层连接,触发 http: proxy error: EOF。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 必须重写
Director函数,显式设置req.Host和req.URL.Host,否则后端 Nginx 可能 404 - 禁用默认的 Transport 连接池复用:在
http.Transport中设MaxIdleConnsPerHost = -1或按需设为小值,避免脏连接累积 - 对非 HTTP 后端(如 gRPC、WebSocket),不能用
httputil.ReverseProxy,得换底层网络层控制
如何用 golang.org/x/net/proxy 支持 SOCKS5/TCP 透传转发
多协议网关的核心不是“改写请求”,而是“不解析、只转发”。HTTP/HTTPS 是应用层,而 SOCKS5、TLS 直通、甚至原始 TCP 流,需要跳过 HTTP 解析栈,走 net.Conn 级别桥接。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 监听时用
net.Listen("tcp", ":8080"),接受连接后立即go handleTCPConn(clientConn, backendAddr) - 不要用
http.Serve,避免 HTTP parser 干预;用io.Copy双向转发,但注意加超时:clientConn.SetDeadline(time.Now().Add(30 * time.Second)) - 若需 SOCKS5 认证或协议协商,用
golang.org/x/net/proxy的FromURL构建 dialer,再传给proxy.Dial,而非直接net.Dial
func handleTCPConn(client net.Conn, backend string) {
defer client.Close()
server, err := net.Dial("tcp", backend)
if err != nil { return }
defer server.Close()
<pre class="brush:php;toolbar:false;">done := make(chan error, 2)
go func() { done <- io.Copy(server, client) }()
go func() { done <- io.Copy(client, server) }()
<-done; <-done // 等待任一方向结束}
gRPC 和 HTTP/2 后端怎么避免 ALPN 协商失败
gRPC 默认走 HTTP/2 over TLS,但 Go 的 ReverseProxy 不支持 ALPN 协商,直连会卡在 TLS 握手阶段,报错 tls: client didn't provide a certificate 或 http2: Framer got bogus frame。关键不在证书,而在客户端和网关之间用了 h2c(明文 HTTP/2)但网关没启用 h2c 支持。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 对 gRPC 后端,用
google.golang.org/grpc/credentials/insecure+grpc.WithTransportCredentials显式指定不加密,再配合http2.ConfigureServer开启 h2c - 不要复用同一个
http.Server同时处理 HTTP/1.1 和 h2c —— 它们监听逻辑冲突,会导致某些请求被静默拒绝 - 若必须 TLS 终止,网关侧需调用
http2.ConfigureTransport配置下游 Transport,且tls.Config.NextProtos = []string{"h2"}
路由匹配时 Host、Path、Header 条件怎么组合才不漏判
单纯用 strings.HasPrefix(req.URL.Path, "/api/v1") 会误伤 /api/v10;只看 req.Host 又忽略 X-Forwarded-Host 场景。真实网关必须支持多维条件 AND 匹配,且优先级明确。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 把匹配逻辑封装成结构体,字段包括
HostPattern(支持通配符*.example.com)、PathPrefix(带结尾/自动补全)、HeaderMatch(map[string]string,空字符串表示存在即匹配) - 匹配顺序必须固定:先 Host → 再 Header → 最后 Path;任意一级不匹配就跳过该规则,不 fallback
- 避免正则:用
path.Match处理简单通配,复杂路径交给gorilla/mux的Router做子路由,别自己写 Regexp
最常被忽略的是 TLS SNI 域名和 HTTP Host 头不一致的场景 —— 比如客户端直连 IP + SNI=api.example.com,但 Host 头填了 localhost。这时候仅靠 req.Host 判断会失效,得从 http.Request.TLS.ServerName 取 SNI 值参与匹配。


















