gRPC拦截器(一)

什么是拦截器

gRPC拦截器(interceptor)是个函数,它可以在gRPC执行服务之前和之后执行一些逻辑,例如认证、授权、日志记录、监控和统计等

函数作为实参传递

在 Go 中,一个普通的函数长这样:

go 复制代码
func Service(request Request) (Response, error)

作用就是传入请求,完成处理后返回响应,有错误就返回 error

作为一门函数式编程语言,同样函数也支持把函数当实参传入另一个函数中,比如

go 复制代码
type Handler func(request Request) (Response, error)

把最开始的函数签名定义成 Handler ,那么我们就可以这样来使用:

go 复制代码
func WrapHandler(request Request, handler Handler) (Response, error) {
  ...... // pre
  resp, err := handler(request)
  ...... // post
  return resp, err
}

如何,相当于我们把实际需要实现的业务逻辑提取到外部定义,这样就可以在实际处理业务之前或者之后定义一些额外的逻辑。

拦截器原理

现在来抽象一下,把 Service 方法弄个称呼:业务方法 ,函数签名如下

go 复制代码
func Service(ctx Context, req any) (any, error)

然后给这个签名定义一个类型

go 复制代码
type Handler func(ctx Context, req any) (any, error)

记住,Handler 代表的就是一个最终的业务方法实现,现在把这个业务方法作为实参传递到另一个函数中去

go 复制代码
func Intercept(ctx Context, req any, handler Handler) (any, error) {
  ......                             // 前处理
  resp, err := Handler(ctx, request) // 调用业务方法
  ......                             // 后处理
  return resp, err
}

完事,这就是拦截器的原理:在业务方法之前或者之后增加额外的逻辑,这就是某些支持注解的语言(Java、TS)的原理,或者把当作装饰设计模式看待。

拦截器链原理

再来抽象一下,把 Intercept 也弄个称呼:拦截器 ,函数签名如下

go 复制代码
type Interceptor func(ctx Context, req any, handler Handler) (any, error)

记住,Interceptor 代表的就是一个拦截器,在调用业务方式时增加前处理和后处理逻辑。

那么问题来了,如果我定义了两个拦截器:

go 复制代码
func Interceptor1(ctx Context, req any, handler Handler) (any, error) {
  return nil, nil
}

func Interceptor2(ctx Context, req any, handler Handler) (any, error) {
  return nil, nil
}

业务方法好像?似乎?也许?只能放到 拦截器 1 或者 拦截器 2 其中一个执行,然后返回响应。但是我想业务方法先流过拦截器 1,然后流过拦截器 2,这样就可以不断新增拦截器逻辑,而不必全堆在一个拦截器中实现全部额外逻辑。

第一版

那有没有办法实现这种逻辑呢?听我说,有的兄弟,有的。

go 复制代码
func WrapInterceptors(ctx Context, req any, handler Handler) (any, err) {
  interceptor1 := func(ctx Context, req any, handler Handler) (any, err) {
    ...... // 1 -> pre
    interceptor2 := func(ctx Context, req any, handler Handler) (any, err) {
      ...... // 2 -> pre
      resp, err := handler(ctx, req)  // 业务方法
      ...... // 2 -> post
      return resp, err
    }
    ...... // 1 -> post
    return interceptor2(ctx, req, handler)
  }
  return interceptor1(ctx, req, handler)
}

resp, err := WrapInterceptors(ctx, req, handler)

怎么样兄弟,满不满足要求?什么?你说这样的实现太丑陋了?要高级点?好吧,听我说,有的兄弟,有的。

第二版

先改动一下拦截器的函数签名,让拦截器返回业务处理函数,记住更改签名后,拦截器是包装业务函数的函数,返回也是业务函数

go 复制代码
type Interceptor func(handler Handler) Handler

拦截器的形式改成闭包形式:

go 复制代码
func Interceptor1(handler Handler) Handler {
  return func(ctx Context, req any) (any, error) {
    ...... // pre-processing
    resp, err := handler(ctx, req)
    ...... // post-processing
    return response, err
  }
}

多个拦截器装饰在一起就完事了

go 复制代码
func WrapInterceptors(ctx Context, req any, handler Handler) (any, error) {
  wrap := Interceptor1(Interceptor2(handler))
  return wrap(ctx, req)
}

这也是Go里面HTTP的拦截器写法,如何兄弟,收不收货?什么?你说这样写还是很丑陋。好吧,要求真多,不过你听我说,有的兄弟,还是有的。

第三版

一些前缀知识

go 复制代码
// 切片的最后一个元素
last := slice[len(slice)-1]

// 移除切片的最后一个元素
slice := slice[:len(slice)-1]

然后只能祭出 闭包 + 递归 大法了

go 复制代码
func WrapInterceptors(interceptors ...Interceptor) Interceptor {
  if len(interceptors) == 1 {
    return interceptors[0]
  }
  return func(handler Handler) Handler {
    last := len(interceptors) - 1
    wrap = interceptors[last](handler) // 从最后一个拦截器开始包装业务方法
    return WrapInterceptors(interceptors[:last]...)(wrap)
  }
}

怎样兄弟?什么?你说倒序太难看懂,行吧,有的,顺序也有的

go 复制代码
func WrapInterceptors(interceptors ...Interceptor) Interceptor {
  if len(interceptors) == 1 {
    return interceptors[0]
  }
  return func(handler Handler) Handler {
    wrap := WrapInterceptors(interceptors[1:]...)(handler)
    return interceptors[0](wrap)
  }
}
go 复制代码
func main() {
  interceptor := WrapInterceptors(Interceptor1, Interceptor2)
  resp, err := interceptor(handler)(ctx, req)
  ......
}

兄弟兄弟,怎么样,是时候收收货了吧?什么?你还想不改动拦截器的签名?特喵的,屁事还真多,行,没问题。

最终版

go 复制代码
func WrapHandler(ctx Context, req any, handler Handler, interceptors ...Interceptor) Handler {
  if len(interceptors) == 0 {
    return handler
  }
  return func(ctx Context, req any) (any, error) {
    wrap := WrapHandler(ctx, req, handler, interceptors[1:]...)
    return interceptors[0](ctx, req, wrap)
  }
}

多个拦截器直接传入即可

go 复制代码
func main() {
  fn := WrapInterceptors(ctx, req, handler, Interceptor1, Interceptor2)
  resp, err := fn(ctx, req)
  ......
}

兄弟,夜里小心走路脚滑。

其实gRPC的拦截器就是以上的方式实现的,就是定义拦截器的时候多了几个形参而已,原理就是那么点事,下次聊聊gRPC的拦截器。

相关推荐
炒空心菜菜37 分钟前
SparkSQL 连接 MySQL 并添加新数据:实战指南
大数据·开发语言·数据库·后端·mysql·spark
蜗牛沐雨3 小时前
Rust 中的 `PartialEq` 和 `Eq`:深入解析与应用
开发语言·后端·rust
Python私教3 小时前
Rust快速入门:从零到实战指南
开发语言·后端·rust
秋野酱4 小时前
基于javaweb的SpringBoot爱游旅行平台设计和实现(源码+文档+部署讲解)
java·spring boot·后端
小明.杨4 小时前
Django 中时区的理解
后端·python·django
有梦想的攻城狮4 小时前
spring中的@Async注解详解
java·后端·spring·异步·async注解
qq_12498707534 小时前
原生小程序+springboot+vue医院医患纠纷管理系统的设计与开发(程序+论文+讲解+安装+售后)
java·数据库·spring boot·后端·小程序·毕业设计
lybugproducer5 小时前
浅谈 Redis 数据类型
java·数据库·redis·后端·链表·缓存
焚 城5 小时前
.NET8关于ORM的一次思考
后端·.net
撸猫7917 小时前
HttpSession 的运行原理
前端·后端·cookie·httpsession