Go实现API网关应优先用标准库httputil.NewSingleHostReverseProxy+chi路由,而非自研;需正确重写Director中URL和Host,注意中间件顺序、负载均衡健康检查及超时/熔断防护。

Go 语言实现 API 网关路由,核心不在“能不能”,而在“要不要自己造”。除非有特殊协议转换、深度流量染色或定制化策略需求,否则直接用 gorilla/mux 或 chi 做反向代理层 + 路由分发,比从零写网关更稳、更易维护。
用 net/http + httputil.NewSingleHostReverseProxy 实现基础路由转发
这是最轻量、最可控的起点。Go 标准库的 httputil.NewSingleHostReverseProxy 已封装好连接复用、header 透传、超时控制等细节,你只需决定“哪个路径打到哪台后端”。
常见错误是忽略 Director 函数里对 req.URL 的重写——不改 Host 和 Path,请求会原样发给后端,导致 404 或路由错乱:
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: "127.0.0.1:8081",
})
proxy.Director = func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = "127.0.0.1:8081"
// 必须重写路径:把 /api/user/... 映射为 /user/...
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/api")
// 还要重写 Host header,否则后端可能拒绝非预期 Host
req.Host = "127.0.0.1:8081"
}
- 所有后端服务必须监听明确 IP:Port,不能依赖 localhost(容器部署时会失效)
-
Director中不要做阻塞操作(如 DB 查询),否则拖慢整个代理链路 - 若需保留原始 Host,用
req.Header.Set("X-Forwarded-Host", req.Host)透传,后端自行解析
用 chi 管理多级路由与中间件注入
chi 比 gorilla/mux 更适合网关场景,因它原生支持子路由器(chi.NewRouter())、中间件链式注册和路径通配,方便按服务维度切分路由表。
立即学习“go语言免费学习笔记(深入)”;
典型结构是:根路由注册鉴权/限流中间件,子路由绑定具体服务代理:
r := chi.NewRouter()
r.Use(authMiddleware, rateLimitMiddleware)
// 用户服务路由
userRouter := chi.NewRouter()
userRouter.Use(logRequest)
userRouter.Handle("/*", userProxy) // 注意通配符写法
r.Mount("/api/users", userRouter)
// 订单服务路由
orderRouter := chi.NewRouter()
orderRouter.Use(validateOrderHeader)
orderRouter.Handle("/*", orderProxy)
r.Mount("/api/orders", orderRouter)
-
Mount会自动剥离前缀,所以userProxy的Director只需处理/users/...段,不用再切/api - 中间件顺序很重要:鉴权必须在路由匹配之后、代理之前执行;日志类中间件建议放在最外层
- 避免在中间件里修改
req.Body(如解密/校验 payload),会破坏流式传输,改用io.TeeReader或提前读取缓存
负载均衡不能只靠随机选节点
Go 本身不提供内置负载均衡器,httputil.NewSingleHostReverseProxy 只支持单目标。你需要自己实现多目标选择逻辑,并考虑健康检查与连接复用。
最简方案是封装一个 RoundRobinProxy 结构体,维护后端列表与当前索引:
type RoundRobinProxy struct {
backends []string
mu sync.RWMutex
idx int
}
func (r *RoundRobinProxy) Director(req *http.Request) {
r.mu.Lock()
backend := r.backends[r.idx]
r.idx = (r.idx + 1) % len(r.backends)
r.mu.Unlock()
u, _ := url.Parse("http://" + backend)
req.URL.Scheme = u.Scheme
req.URL.Host = u.Host
req.Host = u.Host
}
- 真实场景中必须加健康检查:定期
HEAD探活,失败节点临时剔除并记录日志 - 不要用
time.Sleep做探活间隔,改用time.Ticker防止 goroutine 泄漏 - 如果后端使用 gRPC,需换用
grpc-go的round_robinbalancer,HTTP 和 gRPC 的 LB 机制不通用
真正难的不是路由转发或轮询算法,而是故障传播控制——比如某个下游服务卡死,如何避免耗尽网关的 goroutine 和连接池。这需要组合使用 context.WithTimeout、http.Transport 的 MaxIdleConnsPerHost 和熔断器(如 sony/gobreaker)。这些细节一旦漏掉,压测时第一个倒下的就是网关本身。











