gRPC 拦截器那点事,希望帮到你(六)

  • 上一篇介绍了gRPC的接口认证,我们客户端需要实现gRPC提供的接口,然后在服务端业务接口实现中通过metadata获取认证信息,进行判断,那么当我们有几十个,几百个业务接口时,如果都在接口实现中去做,那将是一个噩梦,也不符合DRY(Don't Repeat Yourself)原则,今天一起来看看如何通过gRPC的拦截器做到统一接口认证工作

初识gRPC拦截器

  • gRPC在grpc包中定义了一个变量,如下,这个变量叫做UnaryServerInterceptor(一元服务拦截器)
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, 
        handler UnaryHandler)  (resp interface{}, err error
)
  • 其类型是一个函数,这个函数有,4个入参,两个出参,介绍如下
  • ctx context.Context 上下文
  • req interface{} 用户请求的参数
  • info UnaryServerInfo RPC 方法的所有信息,定义如下
type UnaryServerInfo struct {        
// Server is the service implementation the user provides. 
// This is read-only.        
    Server interface{}        
// FullMethod is the full RPC method string,
// package.service/method.        
    FullMethod string
}
  • handler UnaryHandler RPC方法本身
  • resp interface{} RPC方法执行结果
type UnaryHandler func(ctx context.Context,  req interface{}) (interface{}, error)

如何使用

  • 首先定义一个拦截器
//声明一个变量
var serverIntercept  grpc.UnaryServerInterceptor
 //为这个变量赋值
serverIntercept = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, 
        handler grpc.UnaryHandler) (resp interface{}, err error) {
         //这个方法的具体实现
         err = check(ctx) //权限校验
         if err != nil {
              return
         }
         // 校验通过后继续处理请求
         return handler(ctx, req)
}
  • 在服务端启动时将拦截器添加进去
 //将拦截器添加进去
gRpcServer :=grpc.NewServer(
    []grpc.ServerOption{grpc.UnaryInterceptor(serverIntercept)}...
)
  • 如上就是服务端使用拦截器的所有步骤,客户端在访问服务端时就会被拦截

如何添加多个拦截器

  • 有人说我看上面代码grpc.NewServer是一个可变参数,我传多个不就好了吗?整的是这样吗,我们来试试,代码如下我们添加了两个拦截器
gRpcServer :=grpc.NewServer(
    []grpc.ServerOption{
    grpc.UnaryInterceptor(serverIntercept1),
    grpc.UnaryInterceptor(serverIntercept2)}...
)
  • 很遗憾,服务端启动失败,报错信息如下,什么含义呢,意思是说,这个一元服务拦截器只能设置一个,不能重复,其实从名字就能看出,一元拦截器,就是说只能设置一个拦截器,gRPC有意的阻止拦截器链的形式

panic: The unary server interceptor was already set and may not be reset. [recovered]
panic: The unary server interceptor was already set and may not be reset.

  • 那我们如果真的需要拦截器链,该如何配置呢,核心思想是递归,代码如下:
func InterceptChain(intercepts...grpc.UnaryServerInterceptor)grpc.UnaryServerInterceptor{
    //获取拦截器的长度
    l := len(intercepts)
    //如下我们返回一个拦截器
    return func(ctx context.Context, req interface{},
        info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error){
        //在这个拦截器中,我们做一些操作
        //构造一个链
        chain  := func(currentInter grpc.UnaryServerInterceptor,currentHandler grpc.UnaryHandler) 
        grpc.UnaryHandler {
            return func(currentCtx context.Context,
                currentReq interface{})(interface{}, error) {
                return currentInter(
                    currentCtx,
                    currentReq,
                    info,
                    currentHandler)
            }
        }
        //声明一个handler
        chainHandler := handler
        for i :=  l-1; i >= 0; i-- {
            //递归一层一层调用
            chainHandler = chain(intercepts[i],chainHandler)
        }
        //返回结果
        return chainHandler(ctx,req)
    }
}

gRPC还有哪些拦截器

统一在grpc包下,其他拦截器如下
  1. type UnaryClientInterceptor

    这是一个客户端上的拦截器,在客户端真正发起调用之前,进行拦截,这是一个实验性的api,这是gRPC官方的说法

  2. type StreamClientInterceptor

    在流式客户端调用时,通过拦截clientstream的创建,返回一个自定义的clientstream,可以做一些额外的操作,这是一个实验性的api,这是gRPC官方的说法

  3. type UnaryServerInterceptor (就是上面我们demo中的拦截器)
  4. type StreamServerInterceptor

    拦截服务器上流式rpc的执行

gRPC的拦截分类

  • 按功能来分
    1. 一元拦截器 UnaryInterceptor
    2. 流式拦截器 StreamInterceptor
  • 按端来分
    1. 客户端拦截器 ClientInterceptor
    2. 服务端拦截器 ServerInterceptor
  • 大家平时使用时灵活使用即可,慢慢的就会融会贯通

欢迎大家关注微信公众号:“golang那点事”,更多精彩期待你的到来

gRPC 拦截器那点事,希望帮到你

本作品采用《CC 协议》,转载必须注明作者和本文链接
那小子阿伟
讨论数量: 1
Mosquito

牛的

6个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!