Kratos中间件必须返回middleware.Handler,其签名是func(ctx context.Context, req any) (any, error);req是反序列化后的结构体而非*http.Request,需用transport.FromServerContext(ctx)获取元信息,中间件执行顺序为从右往左嵌套。

中间件函数签名必须返回 middleware.Handler
Kratos 的中间件不是 HTTP 中间件那种 func(http.Handler) http.Handler,而是统一抽象为 middleware.Middleware 类型:它接收一个 middleware.Handler,返回另一个 middleware.Handler。这个 handler 的签名是 func(ctx context.Context, req any) (any, error),和业务方法一致。
常见错误是直接写成 func(http.ResponseWriter, *http.Request) 或试图操作 *http.Request——Kratos 的 transport 层做了封装,你拿到的 req 是反序列化后的结构体(比如 *v1.GetUserRequest),不是原始 HTTP 请求。
- 别对
req做(*http.Request)断言,会 panic - 要获取 transport 元信息(如 method、path),用
transport.FromServerContext(ctx),不是r.URL.Path - 如果需要读取原始请求体(比如做签名验签),得在 transport 层提前读取并缓存,不能在 middleware 里再读
Body(已关闭)
中间件注册顺序决定执行方向
Kratos 的 http.Middleware() 或 grpc.Middleware() 接收多个中间件,但执行顺序是「从右往左」嵌套:最后一个参数最先执行,第一个参数最后执行。这和 Express/Koa 的「从上到下」直觉相反。
例如:http.Middleware(A(), B(), C()),实际调用链是 C → B → A → handler。所以日志、tracing 这类「包裹型」中间件应放在靠后位置(如 C),而鉴权、限流这类「守门型」中间件应放靠前(如 A),否则可能连日志都打不出来就拒绝了请求。
- 鉴权中间件必须在 logging 之前,否则被拒的请求不会记录 trace ID
- recover 中间件建议放在最外层(即
http.Middleware()的最后一个参数),才能捕获所有内层 panic - 不要在中间件里调
next()多次,也不会自动重试;想实现重试需自己控制逻辑
ctx.Value() 透传必须手动注入
你在中间件里往 ctx 塞的值(比如解析出的 *User),下游 handler 拿不到,除非你显式调用 context.WithValue() 并把新 context 绑回 transport 请求中。
Kratos 不会自动把中间件里的 ctx 透传给下一个中间件或 handler;每个中间件拿到的都是上游传下来的原始 ctx。所以常见崩溃是 ctx.Value(key) 返回 nil,接着 v.(*User).ID 直接 panic。
- 正确做法:在中间件 handler 内部,做完业务逻辑后,用
ctx = context.WithValue(ctx, key, val),再调next(ctx, req) - 别用
transport.HTTPContext(r)或全局 context,它们和 request 生命周期无关 - 若用 Casbin 做权限检查,
enforcer.Enforce()前必须确保ctx.Value(userKey)已存在且非 nil
panic 不会被框架 recover,必须主动防御
Kratos 对中间件中的 panic 完全不捕获,一旦发生就会冒泡到 main goroutine,导致服务启动失败或直接退出。这不是 bug,是设计选择——避免掩盖严重错误。
高频 panic 场景包括:未校验依赖是否为 nil(如 logger、enforcer)、对 ctx.Value() 结果不做非空判断就强转、调用未初始化的第三方 client。
- 构造函数里检查必要依赖:
if logger == nil { panic("logger required") } - handler 内首次使用 logger 前才初始化:
log := log.NewHelper(logger),别在闭包外初始化 - 所有
ctx.Value()使用前加判断:if u, ok := ctx.Value(userKey).(*User); !ok { return nil, errors.BadRequest("auth", "user not found") }
真正麻烦的不是写错中间件,而是写对了但忘了注入依赖或透传 context —— 这类问题往往只在特定路径触发,上线后才暴露。











