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)
})
}
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 开销。这也是后续缓存一致性章节要解决的核心问题之一。
3.5 四大核心的协同关系
反向代理、中间件链、请求/响应拦截、路由与权限校验并非独立运行,而是形成一个完整的请求处理闭环:
- 路由匹配 确定请求的目标后端和对应的权限规则。
- 中间件链 按顺序执行前置拦截(含权限校验)。
- 反向代理 将请求转发给后端服务。
- 中间件链 执行后置拦截(含审计日志)。
- 反向代理 将响应返回给客户端。