中间件必须实现http.handler接口(如匿名结构体或闭包),签名应为func(http.handler) http.handler,通过嵌套调用(如auth(mw2(mw1(handler))))构建链;需用r.clone()安全修改请求,避免body重复读取或并发竞争,且所有网络操作须基于r.context()派生子ctx以配合http.server生命周期管理。

中间件怎么套进 http.ServeMux 才不丢请求上下文
标准库的 http.ServeMux 不支持中间件链,直接用 HandleFunc 会绕过你写的包装逻辑。必须把中间件转成符合 http.Handler 接口的类型,否则 next.ServeHTTP 一调就 panic:「nil pointer dereference」或者请求根本没进你的日志/鉴权逻辑。
- 别写
func(http.ResponseWriter, *http.Request)然后塞给HandleFunc—— 这只是 handler 函数,不是可嵌套的http.Handler - 中间件函数签名得是
func(http.Handler) http.Handler,返回一个新http.Handler实例 - 最简封装就是用匿名结构体或闭包实现
ServerHTTP方法,比如:type loggingHandler struct { next http.Handler } func (h loggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Printf("req: %s %s", r.Method, r.URL.Path) h.next.ServeHTTP(w, r) } - 如果用闭包,注意捕获的
next必须是非 nil;常见错误是传了 nilhttp.Handler给中间件,运行时报panic: runtime error: invalid memory address
为什么 http.HandlerFunc 转 http.Handler 后还能链式调用
http.HandlerFunc 是函数类型,但它实现了 http.Handler 接口(有 ServeHTTP 方法),所以能被中间件接收和返回。关键在「转换时机」:中间件不关心里面是 struct 还是函数,只认接口。
- 中间件输入是
http.Handler,输出也必须是http.Handler;http.HandlerFunc(f)就是把普通函数f变成满足接口的实例 - 链式调用本质是嵌套:
auth(mw2(mw1(handler))),最里层handler是最终业务逻辑,外层每次 wrap 都新增一层逻辑 - 性能上无额外开销——接口调用是静态绑定,Go 编译器能内联简单中间件;但别在中间件里做同步阻塞操作(如未设 timeout 的 HTTP 调用),会卡住整个链
- 兼容性没问题:所有 Go 版本都支持,
http.Handler接口自 1.0 未变
net/http 中间件里怎么安全读取和修改 *http.Request
不能直接改 r.URL.Path 或 r.Header 后就 return,下游 handler 可能依赖原始值。要改就得用 r.Clone(),否则并发下会出数据竞争或脏写。
- 读取 body 前必须先
r.Body = ioutil.NopCloser(bytes.NewBuffer(buf)),否则后续 handler 再读就是空 —— 因为Body是单次读取流 - 修改 path、query 或 header 应该新建 request:
newReq := r.Clone(r.Context()) newReq.URL.Path = "/api/v2" + r.URL.Path newReq.Header.Set("X-Processed", "true") - 不要在中间件里调
r.ParseForm()或r.ParseMultipartForm(),除非你确定下游不需要原始 raw body;否则会提前消费 body,导致json.Decode失败 - 常见错误现象:
http: panic serving [::1]:54321: invalid memory address or nil pointer dereference,往往是因为中间件里用了已关闭的r.Body
标准库中间件链如何跟 http.Server 生命周期对齐
中间件本身没生命周期管理,但它的行为受 http.Server 的 ReadTimeout、IdleTimeout 和 Handler 字段影响。如果中间件里做了耗时操作(比如 DB 查询、远程调用),没主动控制超时,就会拖垮整个 server。
立即学习“go语言免费学习笔记(深入)”;
-
http.Server的Handler字段填的是最外层中间件返回的http.Handler,它决定了所有请求入口;别漏掉这一步,否则启动后全是 404 - 中间件里的网络调用必须带 context:用
r.Context()派生子 ctx,并传给下游 client;否则Server.Shutdown()时请求不会中断,变成 goroutine 泄漏 - 不要在中间件里启动 goroutine 处理请求(如
go handleAsync(r)),除非你手动管理其生命周期;标准库的 handler 要求同步完成ServeHTTP - 容易被忽略的一点:中间件顺序决定执行顺序,比如
recovery必须包在最外层,否则 panic 会直接透出,而logging放最里层就看不到其他中间件是否执行成功










