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或直接写入响应,终止后续中间件执行。
权限审计网关的典型中间件链(按注册顺序):
- 恢复中间件(panic 恢复)
- 请求日志中间件(记录原始请求)
- 认证中间件(校验 Token)
- 权限中间件(校验 RBAC/ABAC)
- 审计中间件(记录请求/响应摘要)
- 限流中间件(令牌桶/漏桶)
- 反向代理中间件(转发到后端)
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 或仅记录响应头与状态码,避免全量缓存。
3.4 核心概念对比总结
| 概念 | 职责 | Go实现关键 | 审计网关中的作用 |
|---|---|---|---|
| 反向代理 | 流量转发、负载均衡 | httputil.ReverseProxy |
将审计后的请求转发到后端,隐藏后端拓扑 |
| 中间件链 | 可组合的请求处理管道 | func(http.Handler) http.Handler |
按序执行认证、权限、审计、限流等策略 |
| 请求拦截 | 在转发前注入逻辑 | ServeHTTP 前置代码 |
提取请求元数据、注入审计ID、校验Token |
| 响应拦截 | 在返回前捕获结果 | 包装 ResponseWriter |
记录响应状态码、大小、耗时,用于审计日志 |
架构决策提示:在权限审计网关中,建议将“审计拦截”放在中间件链的最内层(紧邻反向代理),这样能捕获到认证、权限等前置中间件的处理结果,形成完整的请求-响应审计记录。若放在外层,则可能因前置中间件拒绝请求而丢失审计信息。