中间件必须返回http.handler而非http.handlerfunc,因http.handle等标准库函数仅接受http.handler接口类型;正确签名是func(http.handler) http.handler,内部用http.handlerfunc包装逻辑并确保返回值实现servehttp方法。

中间件必须返回 http.Handler,不是 http.HandlerFunc
很多刚写 Go 中间件的人会误以为中间件函数可以返回 http.HandlerFunc,然后直接传给 http.Handle。这是错的——http.Handle 只接受 http.Handler 类型,而 http.HandlerFunc 是一种类型转换器,它本身不是接口实现体,只有调用 http.HandlerFunc(fn).ServeHTTP 才能真正满足接口。中间件函数签名必须是 func(http.Handler) http.Handler,否则链式嵌套时类型不匹配,编译失败。
常见错误现象:cannot use middleware(next) (type http.HandlerFunc) as type http.Handler in argument to middleware
- 正确写法:中间件内部用
http.HandlerFunc包裹逻辑,但最终 return 的必须是实现了ServeHTTP方法的值(即http.Handler) - 错误写法:
return http.HandlerFunc(...)单独作为返回值,没注意它只是类型别名,不是接口实例 - 更安全的写法是显式构造:用
return &myHandler{next: next}或直接返回闭包转成的http.Handler
next.ServeHTTP(w, r) 必须在 defer 前还是后?
请求后逻辑(比如记录耗时、写响应头)要放在 next.ServeHTTP(w, r) 之后,但不能简单写在后面就完事——因为 next 可能 panic(比如 handler 里直接 panic("db down")),导致后续代码不执行。所以实际中常配合 defer + recover 使用,但要注意顺序:
- 日志开始时间必须在
next.ServeHTTP前获取 - 结束时间、状态码捕获等必须在
defer函数里做,且该defer要在next.ServeHTTP调用前注册 - 不要把
next.ServeHTTP放进defer,否则变成“延迟执行”,整个中间件就失效了
示例片段:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
func logging(next http.Handler) http.Handler {<br> return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {<br> start := time.Now()<br> // 包装 ResponseWriter 以便捕获 status code<br> rw := &responseWriter{w: w}<br> defer func() {<br> log.Printf("%s %s %d %v", r.Method, r.URL.Path, rw.status, time.Since(start))<br> }()<br> next.ServeHTTP(rw, r)<br> })<br>}
多个中间件嵌套时,执行顺序容易搞反
中间件 A 包裹 B,B 包裹 C,那么请求进来时顺序是 A → B → C,响应返回时是 C → B → A。这个“洋葱模型”不是靠调用顺序决定的,而是靠闭包捕获的 next 链。最容易踩的坑是手动嵌套写成:A(B(C(handler))),结果发现日志里 C 的 “in” 出现在 A 的 “out” 之后——说明顺序写反了。
- 正确组合:最外层中间件最先执行,应放在最左边,如
cors(auth(logging(handler))) - 避免手写嵌套三层以上,可封装一个
chain工具函数:Chain(m1, m2, m3).Then(handler) - 调试技巧:每个中间件前后加
fmt.Printf("[M1] in / out"),观察输出顺序是否符合洋葱模型
标准库中间件和 chi/gin 等框架的 HandlerFunc 不兼容
chi 的 func(http.ResponseWriter, *http.Request) 和标准库的 http.HandlerFunc 表面一样,但 chi 的中间件签名是 func(http.Handler) http.Handler,和标准库一致;而 gin 完全自建体系,用 gin.HandlerFunc 和 c.Next(),不能混用。如果项目从标准库迁移到框架,中间件几乎都要重写。
- 标准库中间件无法直接塞进
gin.Engine.Use(),会编译报错类型不匹配 - 想复用逻辑?把核心逻辑抽成独立函数,再分别适配不同框架的中间件壳
- 跨框架复用最稳的方式:只共享业务逻辑(如 JWT 解析、DB 查询),不共享中间件包装结构
真正难的不是写一个中间件,而是让多个中间件在 panic、重定向、响应体写入、Header 设置这些边界场景下行为一致——这些细节不会出现在 hello world 示例里,但上线后第一个 500 就暴露出来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










