go http中间件必须返回http.handler,否则链路中断;grpc一元拦截器需严格匹配签名并显式挂载;r.body只能读一次,需手动缓存;提前终止必须显式return,避免逻辑错乱或panic。

Go HTTP中间件必须返回http.Handler,否则链路就断了
写成普通函数不返回 handler,或者返回类型写成func(http.ResponseWriter, *http.Request)却没转成http.HandlerFunc,都会导致编译失败或运行时 panic。
常见错误写法:func auth(next http.Handler) { } —— 没返回值,http.ListenAndServe 接收不到有效 handlerfunc auth(next http.Handler) func(http.ResponseWriter, *http.Request) { } —— 类型不匹配,无法直接嵌套
- 正确签名必须是
func(http.Handler) http.Handler或func(http.HandlerFunc) http.HandlerFunc - 用
http.HandlerFunc包裹闭包是最轻量、最常用的方式 - 如果下游是自定义 struct 实现的
http.Handler(比如实现了ServeHTTP方法),中间件也得接收http.Handler接口,不能只认http.HandlerFunc
gRPC 一元拦截器的签名和调用顺序不能错
grpc.UnaryInterceptor 的函数签名固定为:func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error)
它不是“注册即生效”,而是靠显式传入 grpc.NewServer(grpc.UnaryInterceptor(myInterceptor)) 才挂载;多个拦截器要用 grpc.ChainUnaryInterceptor 串联,顺序直接影响逻辑执行先后。
- 鉴权拦截器必须在日志之前:否则未授权请求也会被记录
- 超时拦截器要包住整个链,否则只对自身生效,无法终止下游阻塞
- 所有拦截器都必须显式调用
handler(ctx, req),否则请求中断且无响应 - 别在
handler调用后写log或metrics逻辑却不检查err,panic 可能被吞掉
r.Body 只能读一次,缓存和重置必须手动做
HTTP 中间件里想读请求体(比如解析 JSON 或校验签名),r.Body 是 io.ReadCloser,一旦被 io.ReadAll(r.Body) 消费,下游 handler 就会读到空内容——这不是 bug,是设计使然。
调试时想看完整 body,必须手动缓存:
- 先
bodyBytes, _ := io.ReadAll(r.Body) - 再
r.Body = io.NopCloser(bytes.NewReader(bodyBytes))塞回去(Go ≥ 1.16) - 生产环境慎用,大文件或高并发下易 OOM;建议只采样或只记录长度
r.ContentLength - 别用
r.ParseForm()后再去读r.Body,它内部已经读过一遍了
提前终止必须显式 return,且不能在 next.ServeHTTP() 之后写响应
鉴权失败、参数校验不通过、限流触发——这些场景下你得主动写响应并退出,否则请求会继续往下走,造成逻辑错乱甚至数据泄露。
典型错误:if token == "" { http.Error(w, "Unauthorized", http.StatusUnauthorized) } —— 少了 return,后面 next.ServeHTTP() 仍会执行
- 所有提前终止路径都必须显式
return - 别在
next.ServeHTTP()之后再调用http.Error(),此时 header 可能已 flush,会 panic 报http: multiple response.WriteHeader calls - 建议封装一个
writeError(w, err)工具函数,内部先检查w.Header().Get("Content-Type")是否为空再决定是否写 header
r.Body 的生命周期管理——这两点不出问题则一切正常,一出就是静默失败或 500,很难定位。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











