应选用 gorilla/mux 或 gin 实现网关路由,需重写 ReverseProxy Director 以传递真实 IP 和路径,JWT 鉴权须用 golang-jwt/jwt v5+ 校验过期与签名,熔断需集成 gobreaker 并精准分类错误类型。

用 net/http + http.ServeMux 做基础路由转发太单薄,得换 gorilla/mux 或 gin
纯标准库的 http.ServeMux 不支持路径参数、正则匹配、子路由嵌套,连 /api/v1/users/{id} 这种基本格式都得手动 strings.Split 解析。真要写网关,gorilla/mux 是最轻量靠谱的选择;如果团队已用 gin,它内置的 gin.Engine 也能扛住——但别用 echo 的中间件链做鉴权,它的上下文传递在并发高时容易丢字段。
-
gorilla/mux的Router.HandleFunc支持{id:[0-9]+}这类约束,比手写正则安全 -
gin的gin.Group可以统一加中间件,但注意c.Copy()才能跨 goroutine 安全传上下文 - 别在
http.HandlerFunc里直接http.Redirect转发,会丢失原始请求头(比如Authorization)
reverseProxy 必须重写 Director,否则后端收不到真实 IP 和路径
Go 标准库的 httputil.NewSingleHostReverseProxy 默认把请求原样发过去,后端看到的是网关本机的 RemoteAddr,X-Forwarded-For 也不加。不修 Director,鉴权和限流就全乱套。
- 必须改
req.URL.Scheme和req.URL.Host指向目标服务,否则后端302重定向会跳错地址 - 加
req.Header.Set("X-Real-IP", clientIP),其中clientIP得从X-Forwarded-For或RemoteAddr提取,不能直接信req.RemoteAddr - 如果后端是微服务且依赖路径前缀(如所有请求带
/svc-a/),要在Director里手动截掉再拼接
JWT 鉴权别自己解析 token,用 golang-jwt/jwt v5+ 并校验 time.Now().After(claims.ExpiresAt.Time)
很多人用 strings.Split 拆 token、base64 解码 payload,既不校验签名,也不检查过期时间,等于没鉴权。v5 版本的 golang-jwt/jwt 把 ExpiresAt 改成 time.Time 类型,避免整数时间戳溢出或时区错位。
- 用
jwt.ParseWithClaims(token, &MyClaims{}, keyFunc),keyFunc必须根据token.Header["kid"]动态选密钥,别硬编码一个[]byte -
claims.VerifyExpiresAt(time.Now(), true)的第二个参数是allowZeroTime,生产环境必须设false - 鉴权中间件里别用
defer关闭数据库连接——token 解析失败时连接可能根本没建,defer会 panic
超时和熔断不能只靠 context.WithTimeout,得配 gobreaker 或 sony/gobreaker
单靠 ctx, cancel := context.WithTimeout(req.Context(), 3*time.Second) 只能防单次卡死,挡不住后端服务持续抖动。连续 5 次 503 后还继续发请求,网关自己先被拖垮。
立即学习“go语言免费学习笔记(深入)”;
-
gobreaker.NewCircuitBreaker的ReadyToTrip函数必须检查错误类型:HTTP5xx算失败,401或404不算 - 熔断器状态变化(如
HalfOpen)要打日志,不然问题来了只能翻监控 - 别给每个后端服务建独立
http.Client,复用带Transport的实例,否则文件描述符爆炸
真正难的不是拼出功能,是把 Director 里的路径重写、gobreaker 的错误分类、JWT 的 kid 密钥轮转这三件事串起来还不互相干扰。线上跑着的时候,少一个 req.Header.Del("Connection") 都可能让 Keep-Alive 失效。



















