3、HTTP网关核心概念:反向代理、中间件链、请求/响应拦截原理

HTTP网关是权限审计网关的流量入口与策略执行点。理解其三大核心概念——反向代理、中间件链、请求/响应拦截原理——是构建稳定、可审计网关的基础。本章从原理到实现,逐一拆解这些关键机制。

3.1 反向代理:网关的流量转发基石

反向代理位于客户端与后端服务之间,代表后端接收请求并转发响应。与正向代理(代理客户端)不同,反向代理对客户端透明,客户端只感知网关地址。

特性 正向代理 反向代理
代理对象 客户端(为客户端隐藏身份) 服务器(为服务器隐藏真实地址)
典型用途 翻墙、缓存、访问控制 负载均衡、安全防护、统一入口
客户端感知 需配置代理地址 透明,只知网关地址
网关场景 不适用 核心模式

在Go中实现反向代理,标准库 net/http/httputil 提供了 ReverseProxy 结构体,支持自定义 Director 和 ModifyResponse 函数,用于在转发前后修改请求/响应。

package main

import (
    "net/http"
    "net/http/httputil"
    "net/url"
)

func main() {
    target, _ := url.Parse("http://localhost:8080")
    proxy := httputil.NewSingleHostReverseProxy(target)

    // 自定义 Director:在转发前注入审计头
    originalDirector := proxy.Director
    proxy.Director = func(req *http.Request) {
        originalDirector(req)
        req.Header.Set("X-Audit-Id", "req-12345")
    }

    http.ListenAndServe(":9090", proxy)
}
实践要点:反向代理必须处理 X-Forwarded-For、X-Real-IP 等头部传递,否则后端无法获取真实客户端IP,导致审计日志失准。

3.2 中间件链:可组合的请求处理管道

中间件链是网关的核心设计模式。每个中间件是一个函数,接收 http.Handler 并返回新的 http.Handler,形成洋葱模型(请求从外到内,响应从内到外)。

// 中间件类型定义
type Middleware func(next http.Handler) http.Handler

// 链式组合函数
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// 示例:日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 请求前
        println("Request:", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        // 响应后
        println("Response sent")
    })
}

中间件链的执行顺序至关重要:

  • 请求阶段:按注册顺序从上到下执行(先注册的中间件先处理请求)。
  • 响应阶段:按注册顺序从下到上执行(后注册的中间件先处理响应)。
  • 中断机制:任一中间件可调用 http.Error 或直接写入响应,终止后续中间件执行。
权限审计网关的典型中间件链(按注册顺序):
  1. 恢复中间件(panic 恢复)
  2. 请求日志中间件(记录原始请求)
  3. 认证中间件(校验 Token)
  4. 权限中间件(校验 RBAC/ABAC)
  5. 审计中间件(记录请求/响应摘要)
  6. 限流中间件(令牌桶/漏桶)
  7. 反向代理中间件(转发到后端)
中间件链洋葱模型(请求/响应流向) 客户端 恢复中间件 (Recovery) 日志中间件 (Logging) 认证中间件 (Auth) 审计中间件 (Audit) 后端服务 请求:客户端 → 恢复 → 日志 → 认证 → 审计 → 后端 响应:后端 → 审计 → 认证 → 日志 → 恢复 → 客户端

3.3 请求/响应拦截原理:在转发前后注入逻辑

拦截(Intercept)是网关实现审计、认证、限流等能力的具体手段。Go的 net/http 包提供了两个天然拦截点:

  • 请求拦截点:在 ServeHTTP 方法中,调用 next.ServeHTTP 之前。
  • 响应拦截点:通过包装 http.ResponseWriter,在 Write / WriteHeader 方法中捕获响应状态码、大小和头部。
// 响应拦截器:包装 ResponseWriter 以捕获状态码和响应体
type auditResponseWriter struct {
    http.ResponseWriter
    statusCode int
    body       []byte
}

func (w *auditResponseWriter) WriteHeader(code int) {
    w.statusCode = code
    w.ResponseWriter.WriteHeader(code)
}

func (w *auditResponseWriter) Write(b []byte) (int, error) {
    w.body = append(w.body, b...)
    return w.ResponseWriter.Write(b)
}

// 审计中间件:同时拦截请求和响应
func AuditMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 请求拦截:记录请求信息
        reqSummary := map[string]string{
            "method": r.Method,
            "path":   r.URL.Path,
            "ip":     r.RemoteAddr,
        }

        // 包装 ResponseWriter 以拦截响应
        aw := &auditResponseWriter{ResponseWriter: w, statusCode: 200}
        next.ServeHTTP(aw, r)

        // 响应拦截:记录响应信息
        respSummary := map[string]interface{}{
            "status":  aw.statusCode,
            "size":    len(aw.body),
        }

        // 将审计日志写入数据库或消息队列
        logAudit(reqSummary, respSummary)
    })
}
性能陷阱:在响应拦截中缓存完整响应体(如 aw.body)会大幅增加内存开销。对于大响应(如文件下载),应使用 io.TeeReader 或仅记录响应头与状态码,避免全量缓存。
请求/响应拦截数据流 客户端 请求拦截点 读取请求头/体 注入审计上下文 后端服务 响应拦截点 捕获状态码/头部 记录响应体摘要 审计日志存储 (DB / 消息队列) 请求拦截注入审计ID → 后端处理 → 响应拦截捕获结果 → 异步写入审计日志

3.4 核心概念对比总结

概念 职责 Go实现关键 审计网关中的作用
反向代理 流量转发、负载均衡 httputil.ReverseProxy 将审计后的请求转发到后端,隐藏后端拓扑
中间件链 可组合的请求处理管道 func(http.Handler) http.Handler 按序执行认证、权限、审计、限流等策略
请求拦截 在转发前注入逻辑 ServeHTTP 前置代码 提取请求元数据、注入审计ID、校验Token
响应拦截 在返回前捕获结果 包装 ResponseWriter 记录响应状态码、大小、耗时,用于审计日志
架构决策提示:在权限审计网关中,建议将“审计拦截”放在中间件链的最内层(紧邻反向代理),这样能捕获到认证、权限等前置中间件的处理结果,形成完整的请求-响应审计记录。若放在外层,则可能因前置中间件拒绝请求而丢失审计信息。