Go API网关应复用net/http.ReverseProxy而非手写http.Client.Do(),因其已内置请求/响应流处理、连接池与超时控制;需定制Director修改请求头和目标URL,生产环境须补超时、限流与日志透传。

Go 语言实现简单 API 网关,核心不是造轮子,而是用好 net/http 的 ReverseProxy —— 它已内置转发逻辑,直接复用即可避免重写请求/响应流、连接池、超时控制等易出错环节。
为什么不用自己拼接 HTTP 请求做转发
手写 http.Client.Do() 转发看似可控,但实际会踩多个坑:
- 忽略原始请求的 body 流(如 POST 表单、JSON 流)导致数据丢失或阻塞
- 未透传全部 header(例如
Content-Encoding、Trailer)引发压缩/分块传输异常 - 未处理
1xx信息响应,某些后端服务(如 gRPC-gateway)依赖它 - 连接复用和超时需手动管理,
ReverseProxy已通过http.Transport统一处理
用 httputil.NewSingleHostReverseProxy 快速启动
这是最轻量的起点,适合路由规则固定、目标单一的场景:
import (
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
u, _ := url.Parse("https://www.php.cn/link/97a34e8859e946b5313f18f5f5f4c9f6")
proxy := httputil.NewSingleHostReverseProxy(u)
// 修改请求:添加 X-Forwarded-* 头
proxy.Director = func(req *http.Request) {
req.Header.Set("X-Forwarded-Host", req.Host)
req.Header.Set("X-Forwarded-Proto", "http")
req.URL.Scheme = u.Scheme
req.URL.Host = u.Host
}
http.ListenAndServe(":8080", proxy)}
立即学习“go语言免费学习笔记(深入)”;
注意:Director 是唯一必须定制的钩子;ModifyResponse 可用于改写响应头或状态码,但不要在其中读取 resp.Body(会消耗流)。
支持多后端路由需替换 Director 逻辑
根据 req.URL.Path 或 Host 头决定转发目标,关键点是重写 req.URL 全部字段:
- 调用
u, _ := url.Parse(target)构造新 URL - 设置
req.URL.Scheme = u.Scheme、req.URL.Host = u.Host - 保留原始 path 和 query:默认已继承,无需手动拼接
- 若需路径重写(如
/api/v1/ → /),修改req.URL.Path后再调用req.URL.RawPath同步编码
错误示例:req.URL.Path = strings.Replace(req.URL.Path, "/api/v1", "", 1) 但未处理 RawPath,会导致含中文或特殊字符的路径解码失败。
生产环境必须补上的三件事
开箱即用的 ReverseProxy 缺少生产必需的防护与可观测能力:
-
超时控制:通过自定义
http.Transport设置IdleConnTimeout、ResponseHeaderTimeout,避免后端卡死拖垮网关 -
限流:在
Director前加中间件,用golang.org/x/time/rate按 IP 或 API 路径限制 QPS -
日志透传:记录
req.RemoteAddr、req.Method、req.URL.Path、耗时、后端返回状态码,别只记网关自身错误
真正难的不是转发本身,而是如何让每次转发都可追溯、可熔断、可灰度 —— 这些能力得靠组合标准库组件,而不是替换 ReverseProxy。










