主题
Day 8 技术设计文档 — 中间件与拦截器
拦截器执行流程(洋葱模型)
实际执行顺序:
Recovery.before → AccessLog.before → Handler → AccessLog.after → Recovery.afterChainServerInterceptors 实现
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 的对比
| 维度 | gRPC | miniRPC |
|---|---|---|
| 类型 | Unary + Stream | 仅 Unary |
| 注册方式 | ServerOption / DialOption | 手动组合 |
| 链式调用 | grpc.ChainUnaryInterceptor | ChainServerInterceptors |
| 内置 | 需引入第三方 | AccessLog + Recovery |
miniRPC 的拦截器设计与 gRPC 高度相似,学习 miniRPC 后可以无缝迁移到 gRPC 的拦截器体系。