Skip to content

Day 8 技术设计文档 — 中间件与拦截器

拦截器执行流程(洋葱模型)

实际执行顺序:

Recovery.before → AccessLog.before → Handler → AccessLog.after → Recovery.after

ChainServerInterceptors 实现

go
func ChainServerInterceptors(interceptors ...ServerInterceptor) ServerInterceptor {
    return func(ctx, req, info, handler) (interface{}, error) {
        // 从右到左构建链:最后一个拦截器最先包裹 handler
        chainHandler := handler
        for i := n - 1; i > 0; i-- {
            next := chainHandler
            interceptor := interceptors[i]
            chainHandler = func(ctx, req) (interface{}, error) {
                return interceptor(ctx, req, info, next)
            }
        }
        // 第一个拦截器作为入口
        return interceptors[0](ctx, req, info, chainHandler)
    }
}

构建方式:从右向左递归包裹。interceptors[0] 是最外层(最先执行 before,最后执行 after)。

AccessLog 拦截器

go
func AccessLogServerInterceptor() ServerInterceptor {
    return func(ctx, req, info, handler) (interface{}, error) {
        start := time.Now()
        resp, err := handler(ctx, req)
        log.Printf("[AccessLog] method=%s duration=%v error=%v",
            info.FullMethod, time.Since(start), err)
        return resp, err
    }
}

Recovery 拦截器

go
func RecoveryServerInterceptor() ServerInterceptor {
    return func(ctx, req, info, handler) (resp interface{}, err error) {
        defer func() {
            if r := recover(); r != nil {
                buf := make([]byte, 4096)
                n := runtime.Stack(buf, false)
                log.Printf("[Recovery] panic=%v\n%s", r, buf[:n])
                err = fmt.Errorf("internal error: %v", r)
            }
        }()
        return handler(ctx, req)
    }
}

关键点:

  • 使用命名返回值 err,使 defer 中可以设置返回值
  • runtime.Stack 捕获当前 goroutine 的调用栈
  • 将 panic 转换为普通 error 返回,避免整个进程崩溃

与 gRPC Interceptor 的对比

维度gRPCminiRPC
类型Unary + Stream仅 Unary
注册方式ServerOption / DialOption手动组合
链式调用grpc.ChainUnaryInterceptorChainServerInterceptors
内置需引入第三方AccessLog + Recovery

miniRPC 的拦截器设计与 gRPC 高度相似,学习 miniRPC 后可以无缝迁移到 gRPC 的拦截器体系。