Go中HTTP中间件必须是func(http.Handler) http.Handler函数,用http.HandlerFunc包装闭包,前置后置逻辑分别置于next.ServeHTTP前后,且必须调用next.ServeHTTP否则链断裂。

Go 里没有“中间件”或“过滤器”的内置概念,所有这类能力都靠手动包装 http.Handler 或实现 grpc.UnaryServerInterceptor 等接口达成。写错一层包装、漏调一次 next.ServeHTTP()、或误改 ResponseWriter,请求就直接静默失败或状态码丢失。
怎么写一个能用的 HTTP 中间件函数
必须是接收 http.Handler、返回 http.Handler 的函数,不能只写逻辑不包装。用 http.HandlerFunc 构造最轻量,避免手写完整接口。
- 函数签名固定为
func(http.Handler) http.Handler,比如func(loggingMiddleware http.Handler) http.Handler - 内部用
http.HandlerFunc包一层闭包,把next捕获进去 - 前置逻辑放
next.ServeHTTP(w, r)前,后置逻辑放它后面 - 必须调用
next.ServeHTTP(w, r),否则链断裂,下游 handler 完全不执行 - 不要在调用后还写
w.WriteHeader()或w.Write(),否则触发http: multiple response.WriteHeader calls
示例(日志中间件):
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("→ %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("← %s %s", r.Method, r.URL.Path)
})
}
为什么 request.Body 一读就空?怎么安全过滤请求体
因为 r.Body 是 io.ReadCloser,底层是单次消费的流。你在鉴权中间件里 io.ReadAll(r.Body) 之后,下游业务 handler 再读就是空字节——这是 500 错误最常见来源之一。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 只在必要时读 body:token 通常在
r.Header.Get("Authorization")或r.URL.Query().Get("sign"),优先走这些路径 - 真要读 body,先缓存再重置:
bodyBytes, _ := io.ReadAll(r.Body),然后r.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - 如果还要往 context 里塞数据,记得用
r = r.WithContext(context.WithValue(r.Context(), key, value)),别直接改原*http.Request - 生产环境避免记录完整 body,可只记
len(bodyBytes)或采样 1KB
gRPC 拦截器怎么串成链?Unary 和 Stream 有什么区别
gRPC 没有 “中间件” 一说,只有 UnaryServerInterceptor 和 StreamServerInterceptor 两种拦截器类型,注册方式完全不同,混用会 panic。
-
UnaryServerInterceptor处理普通 RPC(如GetUser(ctx, req)),函数签名含handler grpc.UnaryHandler,必须显式调用handler(ctx, req)向下传递 -
StreamServerInterceptor处理流式方法(如Subscribe(stream)),函数签名含handler grpc.StreamHandler,且无法直接读消息体——要封装grpc.ServerStream才能拦截Recv()/Send() - 用
grpc.ChainUnaryInterceptor(authInterceptor, loggingInterceptor, recoveryInterceptor)顺序组合,越靠前的越先执行(认证必须在日志之前) - 别把 HTTP 中间件那套
ResponseWriter包装思路照搬到 gRPC,gRPC 没有响应 writer 抽象,状态靠status.Error()返回
容易被忽略的三个关键点
一是 context.Context 必须透传,所有中间件修改 context 都要用 r = r.WithContext(...),闭包捕获变量会导致并发请求互相污染;二是 ResponseWriter 包装器必须同时重写 WriteHeader 和 Write,否则流式响应的状态码取不到;三是中间件顺序不是“谁注册早谁先执行”,而是嵌套顺序决定洋葱层数,auth(log(handler)) 和 log(auth(handler)) 行为完全不同——后者连拒绝请求都不会记日志。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










