直接用net/http因其足够满足简单网关的路由转发与基础中间件需求,避免第三方库的冗余配置与隐式行为;通过http.newservemux+函数式中间件链即可精准控制流向,兼顾轻量、可控与调试便利。

为什么直接用 net/http 而不用第三方网关库
因为“简单 API 网关”的核心诉求是路由转发 + 基础中间件(如鉴权、日志),不是服务发现或熔断。Go 自带的 net/http 完全够用,加一层 http.ServeMux 或直接用 http.Handler 组合就能控制流向,避免引入 gorilla/mux 或 traefik 这类重型依赖带来的配置负担和隐式行为。
常见错误是过早抽象:比如一上来就设计插件系统、YAML 配置加载、动态规则热更新——这些在初期不仅没带来灵活性,反而让路由逻辑分散、调试困难。
- 用
http.NewServeMux()就能做路径前缀匹配,满足大部分代理需求 - 所有中间件用函数链式包装
http.Handler,不依赖框架生命周期 - 后端服务地址写死在 map 或结构体里,启动时校验可用性,比运行时解析配置更可控
如何安全地做反向代理而不暴露内部服务细节
Go 标准库的 httputil.NewSingleHostReverseProxy 是起点,但它默认透传所有请求头(包括 X-Forwarded-For、User-Agent),且不自动改写响应体里的绝对 URL。不处理这些,网关会变成“透明通道”,后端服务可能误判客户端 IP,或返回指向自己端口的跳转链接。
关键动作是定制 Director 和重写 ModifyResponse:
-
Director中必须清除原始Host头,设为后端地址的Host;否则后端可能拒绝请求(尤其启用了 Host 白名单) - 手动设置
X-Real-IP和X-Forwarded-Proto,供后端识别真实客户端和协议 -
ModifyResponse里检查Location响应头,把http://localhost:8081/xxx改成/xxx(相对路径)或网关对外域名 - 务必调用
resp.Body.Close(),否则连接不会复用,压测时很快耗尽文件描述符
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.Director = func(req *http.Request) {
req.Header.Set("X-Real-IP", realIP(req))
req.Header.Set("X-Forwarded-Proto", "https") // 根据 TLS 状态动态判断
req.Host = u.Host
req.URL.Scheme = u.Scheme
req.URL.Host = u.Host
}
proxy.ModifyResponse = func(resp *http.Response) error {
if loc := resp.Header.Get("Location"); loc != "" {
if url, err := url.Parse(loc); err == nil && !url.IsAbs() {
resp.Header.Set("Location", url.String())
}
}
return nil
}
怎么给不同路由加不同中间件(比如 /admin 需要 JWT,/public 不需要)
不能全局注册中间件,得按路由粒度组合。最直白的方式是为每个路由路径显式构造 handler 链:原始 handler → 日志 → 鉴权(条件跳过)→ 代理。
典型陷阱是中间件顺序写反:比如把鉴权放在日志之后没问题,但如果把日志放在代理之后,就只能记录到网关自身的 5xx 错误,收不到后端返回的状态码。
- 定义中间件函数类型:
func(http.Handler) http.Handler,保持可组合性 - 对需要鉴权的路径,用
authMiddleware(proxyHandler);对公开路径,直接用proxyHandler - 注意中间件里不要提前
writeHeader或Write,否则后续 handler 会 panic:”http: multiple response.WriteHeader calls” - JWT 验证失败时,应直接
http.Error(w, "Unauthorized", http.StatusUnauthorized)并 return,别再调用 next.ServeHTTP
示例路由注册:
mux := http.NewServeMux()
mux.Handle("/api/", loggingMiddleware(authMiddleware(apiProxy)))
mux.Handle("/health", healthHandler)
mux.Handle("/public/", publicProxy)
为什么健康检查接口不能只 ping 后端服务端口
网关自身健康状态 ≠ 后端服务可达。如果只用 net.DialTimeout 检查后端端口,会忽略后端进程卡死、DB 连接池耗尽、或路由配置错误等场景。真正有效的健康检查必须走完整请求链路。
但也不能每次请求都发一次 HTTP 探针——性能太差。折中做法是:后台 goroutine 定期(如 10 秒)对每个上游服务发一个轻量 HEAD /health,结果缓存在内存 map 中;主请求路径里只查这个 map。
- 缓存需加读写锁(
sync.RWMutex),避免并发读写 panic - 探针超时设为 2 秒以内,避免拖慢整个健康检查周期
- 失败连续 3 次才标记为 down,防止网络抖动误判
-
/health接口返回值建议包含各上游状态,方便运维 curl 一眼看清
最容易被忽略的是:健康检查的请求头要和真实流量一致(比如带 X-Forwarded-For),否则某些后端服务会因 header 校验失败而返回 403。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











