4. 请求转发原理:HTTP反向代理机制、请求头/体转发、路径重写、上游服务发现

API网关的核心职责之一是将客户端请求透明地转发至上游服务。这一过程并非简单的TCP数据拷贝,而是基于HTTP反向代理机制的精细化操作,涉及请求头/体的处理、路径的动态重写以及上游节点的动态发现。理解这些底层原理,是构建稳定、高性能网关的基础。

4.1 HTTP反向代理机制

反向代理位于客户端与上游服务之间,代表上游服务接收请求。与正向代理(代理客户端)不同,反向代理对客户端透明,客户端无需感知上游服务的真实地址。

核心流程: 客户端 → 网关(反向代理) → 上游服务。网关在中间层完成连接管理、协议转换、请求分发与响应聚合。

在Go语言中,标准库 net/http/httputil 提供了 ReverseProxy 结构体,其核心机制如下:

  • 连接池复用: 默认使用 http.DefaultTransport,维护与上游服务的TCP长连接,减少握手开销。
  • 请求克隆: 对原始 *http.Request 进行浅拷贝,修改其 URL、Host、Header 等字段后发送至上游。
  • 错误处理: 提供 ErrorHandler 回调,可自定义上游不可达时的响应策略(如返回502)。
  • 响应修改: 通过 ModifyResponse 回调,可在响应返回客户端前修改响应头或体。
// Go标准库反向代理核心示例
proxy := &httputil.ReverseProxy{
    Director: func(req *http.Request) {
        // 修改请求URL指向上游服务
        req.URL.Scheme = "http"
        req.URL.Host = "upstream-service:8080"
        req.URL.Path = req.URL.Path // 默认透传,可在此重写
        // 设置X-Forwarded-For头
        if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
            req.Header.Set("X-Forwarded-For", clientIP)
        }
    },
    ModifyResponse: func(resp *http.Response) error {
        // 可在此添加全局响应头
        resp.Header.Set("X-Gateway", "Go-Edge")
        return nil
    },
    ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
        http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
    },
}

4.2 请求头与请求体转发

请求转发过程中,请求头和请求体的处理策略直接影响上游服务的正确性。

请求头转发

默认情况下,ReverseProxy 会转发大部分客户端请求头,但会移除 Connection、Upgrade 等逐跳头(Hop-by-hop headers)。网关通常需要执行以下操作:

操作类型 说明 示例
透传 将客户端原始头原封不动转发 Content-Type、Accept
新增 添加网关标识或链路追踪信息 X-Request-Id、X-Trace-Id
改写 修改Host头或认证头 Host → 上游真实Host
移除 删除敏感或内部头 Authorization(若网关已鉴权)
注意: 转发 Authorization 头需谨慎。若网关已完成鉴权,建议移除该头,避免上游服务重复校验或泄露凭据。

请求体转发

请求体转发相对直接:ReverseProxy 会将 req.Body(一个 io.ReadCloser)直接传递给上游。但需注意以下场景:

  • 大文件上传: 避免在网关层缓冲整个请求体,应使用流式转发。Go的 ReverseProxy 默认流式读取,无需额外配置。
  • 请求体修改: 若需在转发前修改请求体(如注入JSON字段),需将原始Body读取到内存,修改后替换为新的 io.ReadCloser。此操作会增加内存开销,建议仅在必要时使用。
  • Content-Length处理: 修改请求体后,必须同步更新 Content-Length 头,否则上游服务可能解析失败。

4.3 路径重写

路径重写是API网关的常见需求,用于将外部路径映射到内部服务路径。例如,将 /api/v1/users 重写为 /users 转发给用户服务。

路径重写策略通常分为三类:

策略 描述 示例(外部→内部)
前缀替换 将路径前缀替换为另一前缀 /api/v1/orders → /orders
正则匹配 使用正则捕获组进行灵活重写 /v2/users/:id → /api/users/{id}
静态映射 将特定路径精确映射到另一路径 /health → /internal/healthz
// 前缀替换示例(在Director中实现)
Director: func(req *http.Request) {
    // 将 /api/v1/* 重写为 /*
    if strings.HasPrefix(req.URL.Path, "/api/v1") {
        req.URL.Path = strings.TrimPrefix(req.URL.Path, "/api/v1")
        if req.URL.Path == "" {
            req.URL.Path = "/"
        }
    }
    req.URL.Host = "user-service:8080"
    req.URL.Scheme = "http"
}
最佳实践: 路径重写应在 Director 函数中完成,因为此时请求尚未发送。避免在 ModifyResponse 中修改路径,那已无意义。

4.4 上游服务发现

在微服务架构中,上游服务的地址通常是动态变化的。网关需要集成服务发现机制,动态获取可用的上游节点。

常见的服务发现方式包括:

  • DNS解析: 利用DNS轮询返回多个IP,简单但无法感知服务健康状态。
  • 注册中心: 集成Consul、Etcd、Nacos等,实时监听服务节点变化。
  • Kubernetes API: 通过K8s Service的Endpoints获取Pod IP列表。
  • 静态配置: 适用于开发或固定节点场景,直接配置IP列表。

下图展示了网关集成服务发现后的请求转发完整链路:

API网关请求转发与服务发现流程 API网关请求转发与服务发现流程 客户端 HTTP请求 API网关 反向代理引擎 路径重写 / 头处理 Director 函数 服务发现 Consul / K8s / DNS 查询节点 节点IP列表 上游服务A 上游服务B 上游服务C 转发请求 关键说明 • 网关通过服务发现获取节点 • Director函数执行路径重写 • 支持负载均衡策略

4.5 负载均衡与健康检查

服务发现获取到节点列表后,网关需要选择合适的节点转发请求。常见的负载均衡算法包括:

算法 特点 适用场景
轮询 依次选择节点,简单公平 各节点性能相近
加权轮询 根据权重分配流量 异构服务器集群
最少连接 选择活跃连接数最少的节点 长连接或请求处理时间差异大
一致性哈希 根据请求特征(如IP、用户ID)哈希 需要会话粘滞或缓存亲和性
健康检查: 服务发现模块应定期对上游节点发起健康检查(如HTTP GET /health),自动剔除不健康节点,避免请求转发到故障实例。Go中可使用 httputil.ReverseProxy 的 Transport 字段结合自定义 http.RoundTripper 实现。

4.6 本章小结

请求转发是API网关的核心能力,涉及反向代理机制、请求头/体处理、路径重写与上游服务发现。Go标准库的 ReverseProxy 提供了强大的基础能力,开发者只需在 Director 函数中定制转发逻辑,即可实现灵活、高效的请求转发。结合服务发现与负载均衡,网关能够构建高可用的微服务流量入口。