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列表。
下图展示了网关集成服务发现后的请求转发完整链路:
4.5 负载均衡与健康检查
服务发现获取到节点列表后,网关需要选择合适的节点转发请求。常见的负载均衡算法包括:
| 算法 | 特点 | 适用场景 |
|---|---|---|
| 轮询 | 依次选择节点,简单公平 | 各节点性能相近 |
| 加权轮询 | 根据权重分配流量 | 异构服务器集群 |
| 最少连接 | 选择活跃连接数最少的节点 | 长连接或请求处理时间差异大 |
| 一致性哈希 | 根据请求特征(如IP、用户ID)哈希 | 需要会话粘滞或缓存亲和性 |
httputil.ReverseProxy 的 Transport 字段结合自定义 http.RoundTripper 实现。
4.6 本章小结
请求转发是API网关的核心能力,涉及反向代理机制、请求头/体处理、路径重写与上游服务发现。Go标准库的 ReverseProxy 提供了强大的基础能力,开发者只需在 Director 函数中定制转发逻辑,即可实现灵活、高效的请求转发。结合服务发现与负载均衡,网关能够构建高可用的微服务流量入口。