创建时间: 2019/9/13 17:03
作者: sunpengwei1992@aliyun.com

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

初识gRPC拦截器

  • gRPC在grpc包中定义了一个变量,如下,这个变量叫做UnaryServerInterceptor(一元服务拦截器),

    1. type UnaryServerInterceptor
    2. func(
    3. ctx context.Context,
    4. req interface{},
    5. info *UnaryServerInfo,
    6. handler UnaryHandler)
    7. (resp interface{}, err error
    8. )
  • 其类型是一个函数,这个函数有,4个入参,两个出参,介绍如下

  • ctx context.Context 上下文
  • req interface{} 用户请求的参数
  • info UnaryServerInfo RPC 方法的所有信息,定义如下

    1. type UnaryServerInfo struct {
    2. // Server is the service implementation the user provides.
    3. // This is read-only.
    4. Server interface{}
    5. // FullMethod is the full RPC method string,
    6. // package.service/method.
    7. FullMethod string
    8. }
  • handler UnaryHandler RPC方法本身

  • resp interface{} RPC方法执行结果

    1. type UnaryHandler func(
    2. ctx context.Context,
    3. req interface{}
    4. ) (interface{}, error)

    如何使用

  • 首先定义一个拦截器

    1. //声明一个变量
    2. var serverIntercept grpc.UnaryServerInterceptor
    3. //为这个变量赋值
    4. serverIntercept = func(
    5. ctx context.Context, req interface{},
    6. info *grpc.UnaryServerInfo,
    7. handler grpc.UnaryHandler
    8. ) (resp interface{}, err error) {
    9. //这个方法的具体实现
    10. err = check(ctx) //权限校验
    11. if err != nil {
    12. return
    13. }
    14. // 校验通过后继续处理请求
    15. return handler(ctx, req)
    16. }
  • 在服务端启动时将拦截器添加进去

    1. //将拦截器添加进去
    2. gRpcServer :=grpc.NewServer(
    3. []grpc.ServerOption{grpc.UnaryInterceptor(serverIntercept)}...
    4. )
  • 如上就是服务端使用拦截器的所有步骤,客户端在访问服务端时就会被拦截

    如何添加多个拦截器

  • 有人说我看上面代码grpc.NewServer是一个可变参数,我传多个不就好了吗?整的是这样吗,我们来试试,代码如下我们添加了两个拦截器

    1. gRpcServer :=grpc.NewServer(
    2. []grpc.ServerOption{
    3. grpc.UnaryInterceptor(serverIntercept1),
    4. grpc.UnaryInterceptor(serverIntercept2)}...
    5. )
  • 很遗憾,服务端启动失败,报错信息如下,什么含义呢,意思是说,这个一元服务拦截器只能设置一个,不能重复,其实从名字就能看出,一元拦截器,就是说只能设置一个拦截器,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.

  • 那我们如果真的需要拦截器链,该如何配置呢,核心思想是递归,代码如下:

    1. //这个方法接受多个拦截器,最终返回一个拦截器
    2. func InterceptChain(intercepts...
    3. grpc.UnaryServerInterceptor)grpc.UnaryServerInterceptor{
    4. //获取拦截器的长度
    5. l := len(intercepts)
    6. //如下我们返回一个拦截器
    7. return func(
    8. ctx context.Context,
    9. req interface{},
    10. info *grpc.UnaryServerInfo,
    11. handler grpc.UnaryHandler) (resp interface{}, err error){
    12. //在这个拦截器中,我们做一些操作
    13. //构造一个链
    14. chain := func(
    15. currentInter grpc.UnaryServerInterceptor,
    16. currentHandler grpc.UnaryHandler) grpc.UnaryHandler {
    17. return func(currentCtx context.Context,
    18. currentReq interface{})(interface{}, error) {
    19. return currentInter(
    20. currentCtx,
    21. currentReq,
    22. info,
    23. currentHandler)
    24. }
    25. }
    26. //声明一个handler
    27. chainHandler := handler
    28. for i := l-1; i >= 0; i-- {
    29. //递归一层一层调用
    30. chainHandler = chain(intercepts[i],chainHandler)
    31. }
    32. //返回结果
    33. return chainHandler(ctx,req)
    34. }
    35. }
  • github上已经有这么一个项目,如下

  • https://github.com/grpc-ecosystem/go-grpc-middleware
  • 这个项目提供了拦截器的interceptor链式的功能,还有其它一些功能,大家可以去学习学习。

    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那点事”,更多精彩期待你的到来
GoLang公众号.jpg