Golang自定义中间件开发 拦截请求处理逻辑
Go语言中间件本质是函数嵌套模式,签名必为func(http.Handler)http.Handler。拦截请求后须显式return以避免继续执行后续中间件。r.Body仅可读一次,需手动缓存后重置。多个中间件构成洋葱模型,顺序决定执行逻辑,数据通过context.WithValue实现上下文传递。
坦白说,在 Go 里写自定义中间件,本质上不是一件多难的事。但说句实话,Go 并没有官方定义的“中间件”这么个东西——它更像是一种代码组织的模式。不过,也正因为这种自由,很多刚接触的朋友容易在一些小细节上栽跟头。今天咱们就挑几个最容易被忽视、但一旦出错就让人头疼的点,好好捋一遍。

中间件函数签名:不是你想怎么写就怎么写
在 Go 里,中间件本质上就是一个函数套函数。你输入一个 http.Handler,它得输出另一个 http.Handler。这套接口很纯粹,但也很严格——一旦写错了签名,等着你的不是编译失败就是运行时的 panic。
比如下面这几种写法,就是典型的“挖坑”:
func auth(next http.Handler) { }—— 没返回值?那http.ListenAndServe根本收不到有效 handler。func auth(next http.Handler) func(http.ResponseWriter, *http.Request) { }—— 类型对不上,没法嵌套进链路。- 正确写法有且只有两种:
func(http.Handler) http.Handler或者func(http.HandlerFunc) http.HandlerFunc。 - 最轻量、最常用的方式就是
http.HandlerFunc包裹闭包:return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ... })。 - 如果你的下游是自定义 struct 实现了
http.Handler(比如实现了ServeHTTP方法),那中间件也得接收http.Handler接口,不能只认http.HandlerFunc。
拦截请求后,别忘了显式 return
实战中,鉴权失败、参数校验不过、限流触发……这些场景你肯定不陌生。这时候,你得主动写响应、然后退出,否则后面的逻辑会继续执行——轻则逻辑错乱,重则数据泄露。
看看这个常见的“坑”:
- 错误写法:
if token == "" { http.Error(w, "Unauthorized", http.StatusUnauthorized) }—— 少了return,后面next.ServeHTTP()仍然会执行。 - 结论很简单:所有提前终止的路径,都必须显式加上
return。 - 千万别在
next.ServeHTTP()之后再调http.Error()。这时候 header 很可能已经被 flush 了,会触发http: multiple response.WriteHeader calls的 panic。 - 建议封装一个
writeError(w, err)工具函数,内部先判断一下w.Header().Get("Content-Type")是否为空,再决定要不要写 header。这样更安全。
r.Body 只能读一次——这个坑,太经典了
Body 只能读一次,这事儿本身是个“坑”。为什么?因为标准库的 r.Body 是 io.ReadCloser——一旦被 io.ReadAll(r.Body) 或者 json.NewDecoder(r.Body).Decode() 消费掉,下游 handler 再读就是空字节。
那怎么办呢?
- 调试时想看完整 body,必须手动缓存:先
bodyBytes, _ := io.ReadAll(r.Body),再r.Body = io.NopCloser(bytes.NewReader(bodyBytes))塞回去。 - 但生产环境要谨慎,大文件或高并发下这么做很容易 OOM。建议只采样,或者只记录
r.ContentLength就够了。 - 注意:别用
r.ParseForm()之后去读r.Body,它内部已经读过一遍了。 - 像 Gin 这样的框架默认做好了 Body 缓存,但原生
net/http没有这个特性——这一点很容易被忽略。
多个中间件嵌套时,next 的指向要心里有数
中间件是典型的“洋葱模型”:最外层中间件的 next 指向第二层,第二层的 next 指向第三层……最内层的 next 才是最终的请求处理器(比如 http.HandlerFunc)。
这里有几个关键点需要注意:
- 手写嵌套时,必须显式拼接:
loggingMiddleware(authMiddleware(handler)),而不是靠“注册”自动生效。 - 顺序一旦颠倒,逻辑就会乱。比如把认证放到日志中间件后面,认证失败时就没有日志可打。
- 如果需要在中间件之间传递数据(比如用户 ID),唯一的办法是
context.WithValue。而且 key 类型要统一,推荐用私有 struct 字段,而不是简单的字符串——否则容易键名冲突。 - 当你的中间件需要配置(比如超时时间、是否开启日志),用结构体封装会比闭包更清晰,尤其是在需要组合或动态配置的场景下。
这些都是看起来不大、但实际开发中一踩一个准的细节。后面有时间,咱们可以接着聊一聊如何设计更优雅的中间件链。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















