3. HTTP 网关核心原理:反向代理、中间件链、请求/响应拦截、路由与权限校验基础

在构建权限审计网关之前,必须透彻理解 HTTP 网关的四大核心支柱:反向代理、中间件链、请求/响应拦截以及路由与权限校验。这四者构成了网关处理请求的完整生命周期,也是后续实现缓存一致性的基础骨架。

3.1 反向代理:网关的入口与出口

反向代理是网关最基础的职责——接收客户端请求,将其转发给后端服务,并将后端响应返回给客户端。与正向代理(代理客户端)不同,反向代理对客户端透明,客户端只感知网关的存在。

核心职责: 协议转换、负载均衡、连接池管理、TLS 终止、请求/响应头改写。
特性 正向代理 反向代理
代理对象 客户端(隐藏客户端身份) 服务端(隐藏后端拓扑)
典型场景 翻墙、内网访问外网 负载均衡、API 网关、CDN
客户端感知 需手动配置代理 无感知,直接请求网关
Go 实现 httputil.ReverseProxy 不适用 httputil.ReverseProxy 核心组件

Go 标准库 net/http/httputil 包提供了开箱即用的 ReverseProxy 结构体,只需指定后端目标 URL 即可完成基础代理。但在权限审计网关中,我们需要在代理前后插入自定义逻辑——这正是中间件链的用武之地。

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

中间件链是网关架构的灵魂。它将请求处理过程拆解为一系列可插拔的处理器(Handler),每个中间件负责一个横切关注点:日志、认证、限流、审计、缓存等。中间件按顺序串联,形成一条处理管道。

// 中间件类型定义
type Middleware func(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
}

// 使用示例
finalHandler := Chain(
    proxyHandler,
    auditMiddleware,
    authMiddleware,
    loggingMiddleware,
)
执行顺序要点: 中间件链的注册顺序与执行顺序相反。上例中,请求先经过 loggingMiddleware,然后是 authMiddleware,最后是 auditMiddleware,才到达 proxyHandler。响应则反向流回。

3.3 请求/响应拦截:在关键节点注入逻辑

拦截是中间件的具体实现手段。网关需要在请求到达后端之前(前置拦截)和响应返回客户端之前(后置拦截)执行特定操作。

  • 前置拦截: 解析 Token、校验权限、提取用户身份、注入追踪 ID、检查缓存命中。
  • 后置拦截: 记录审计日志、改写响应头、缓存响应体、统计耗时。
// 前置拦截示例:提取并校验 Token
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "missing token", http.StatusUnauthorized)
            return
        }
        // 校验 token,提取用户信息注入 context
        ctx := context.WithValue(r.Context(), "user", parsedUser)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 后置拦截示例:记录审计日志
func auditMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        sw := &statusWriter{ResponseWriter: w}
        next.ServeHTTP(sw, r)
        // 响应已发送,记录审计日志
        log.Printf("audit: %s %s -> %d", r.Method, r.URL.Path, sw.status)
    })
}
请求/响应拦截流程 客户端 前置拦截 Token 校验 缓存检查 后端服务 后置拦截 审计日志 缓存写入 响应返回 拦截点说明 • 前置拦截:请求到达后端前执行,用于认证、授权、缓存命中判断 • 后置拦截:后端响应返回后执行,用于审计、缓存写入、响应改写

3.4 路由与权限校验基础

路由决定了请求应该被转发到哪个后端服务,而权限校验决定了该请求是否被允许执行。在网关中,这两者紧密耦合:路由匹配的结果决定了需要应用哪套权限规则。

3.4.1 路由匹配策略

策略 描述 Go 实现方式 适用场景
前缀匹配 按 URL 路径前缀转发,如 /api/v1/* strings.HasPrefix 或 http.ServeMux 微服务划分明确,路径前缀对应服务名
精确匹配 完全匹配 URL 路径 路由表 map 查找 有限数量的固定端点
正则匹配 使用正则表达式匹配路径 regexp 包 需要参数提取,如 /users/{id}
方法+路径组合 同时匹配 HTTP 方法和路径 自定义路由树(Trie) RESTful API 网关

3.4.2 权限校验基础模型

网关层权限校验通常采用 RBAC(基于角色的访问控制) 或 ABAC(基于属性的访问控制)。在 Go 中,权限校验中间件通过从请求上下文提取用户身份,与路由绑定的权限规则进行比对。

// 权限校验中间件骨架
func permissionMiddleware(permissionMap map[string][]string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            user := r.Context().Value("user").(User)
            requiredPerms, exists := permissionMap[r.URL.Path]
            if !exists {
                // 路由未注册权限,默认拒绝
                http.Error(w, "forbidden", http.StatusForbidden)
                return
            }
            if !user.HasAnyPermission(requiredPerms) {
                http.Error(w, "insufficient permissions", http.StatusForbidden)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}
性能陷阱: 路由匹配和权限校验应避免在每次请求时都进行数据库查询。正确的做法是在网关启动时加载路由-权限映射表到内存,或使用本地缓存(如 sync.Map)减少 I/O 开销。这也是后续缓存一致性章节要解决的核心问题之一。
路由与权限校验架构 HTTP 请求 路由匹配器 前缀/精确/正则 路由-权限映射表 /api/v1/* → [read] /admin/* → [admin] 权限校验器 RBAC / ABAC 后端服务 403 拒绝 通过 拒绝 关键设计原则 1. 路由匹配与权限校验解耦:路由只负责分发,权限只负责判断 2. 权限映射表在网关启动时加载,运行时只读,避免锁竞争 3. 拒绝响应应尽早返回,减少不必要的后端调用

3.5 四大核心的协同关系

反向代理、中间件链、请求/响应拦截、路由与权限校验并非独立运行,而是形成一个完整的请求处理闭环:

  1. 路由匹配 确定请求的目标后端和对应的权限规则。
  2. 中间件链 按顺序执行前置拦截(含权限校验)。
  3. 反向代理 将请求转发给后端服务。
  4. 中间件链 执行后置拦截(含审计日志)。
  5. 反向代理 将响应返回给客户端。