<p>用 gorilla/mux + net/http 即可构建轻量可控的路由转发网关:通过 httputil.NewSingleHostReverseProxy 实现健壮反向代理,正确处理 Host、RequestURI、WebSocket 及 X-Forwarded-* 头;Director 仅改写请求目标,禁用阻塞操作;聚合调用需并发+独立超时+WaitGroup;严格校验下游状态码与业务错误,统一错误语义。</p>

用 gorilla/mux + net/http 做最简路由转发网关,别急着上 gin 或 echo
Go 写 API 网关,第一反应常是找“网关框架”,但多数场景下,gorilla/mux 搭配原生 net/http 就够用——轻、可控、没隐藏行为。你不需要抽象层来帮你做反向代理,因为 Go 标准库的 httputil.NewSingleHostReverseProxy 已经足够健壮。
常见错误是直接用 http.HandleFunc 手写转发逻辑,结果漏掉 Host 头透传、Content-Length 清零、或 Upgrade 请求(如 WebSocket)直接失败。
- 转发前必须调用
req.Host = upstreamURL.Host,否则后端看到的是网关自己的 Host - 务必重置
req.RequestURI = "",否则ReverseProxy会拼错路径 - WebSocket 流量需显式检查
Upgrade和Connection头,并跳过默认代理逻辑
ReverseProxy 的 Director 函数里改什么、不改什么
Director 是唯一需要自定义的函数,但它只负责“把请求改写成发给后端的样子”,不是用来做鉴权或限流的地方。很多人在这里塞日志、加 header、甚至调用外部服务,结果拖慢所有请求,还掩盖了超时归因。
典型误用:在 Director 里解析 JWT 并查 DB 验证用户——这会让每个请求多一次网络延迟,且无法并行或缓存。
立即学习“go语言免费学习笔记(深入)”;
- 只改
req.URL(拼接上游地址)、req.Host、必要时加X-Forwarded-*头 - 不要在
Director里做任何阻塞操作,包括time.Sleep、http.Get、db.Query - 如果要透传原始客户端 IP,用
X-Real-IP而非X-Forwarded-For,后者可能被伪造
聚合多个服务响应时,用 sync.WaitGroup + context.WithTimeout 控制并发与超时
网关聚合不是简单串行调用三个 http.Client.Do,而是得并发发、统一收、各自超时。用 goroutine 直接开协程但不等结果,会导致 goroutine 泄漏;用 select 等 channel 但没设超时,会卡死整个请求。
错误现象:某个下游服务 hang 住,网关响应时间从 100ms 暴涨到 30s,且 CPU 持续高。
- 每个下游调用必须绑定独立的
context.WithTimeout(parentCtx, 2*time.Second) - 用
sync.WaitGroup确保所有 goroutine 完成,而不是靠time.After粗暴等待 - 聚合结果结构体字段名要和下游保持一致,避免前端反复适配;字段类型用指针(如
*string)方便区分空值与默认值
上线前必查的三个 HTTP 状态码陷阱
网关返回 502 Bad Gateway 很常见,但真正难排查的是 200 里藏错误、404 不该由网关抛、503 被误当成下游故障。
比如下游返回 200 但 body 是 {"code":500,"msg":"db timeout"},网关若不检查业务 code,就等于把错误吞了;又比如下游服务未注册路由,返回 404,网关不该原样透传,而应转成 400 Bad Request 或自定义错误码。
- 对关键下游接口,强制约定响应格式,网关层做
json.Unmarshal后检查code != 0 -
404响应需判断来源:是网关路由没匹配(应返回404),还是下游真实不存在(建议返回502) -
503必须带Retry-After头,且只在网关自身过载(如连接池满)时返回,不能转发下游的503
真正的难点不在代码怎么写,而在于每个下游服务的协议细节、超时策略、错误语义是否对齐——这些没法靠一个网关框架自动解决,得一条接口一条接口对清楚。










