写给go开发者的gRPC教程-拦截器

科技   2024-08-26 08:45   北京  

gRPC拦截器和其他框架的拦截器(也称middleware)作用是一样的。利用拦截器我们可以在不侵入业务逻辑的前提下修改或者记录服务端或客户端的请求与响应,利用拦截器我们可以实现诸如日志记录、权限认证、限流等诸多功能

上一篇提到gRPC的通信模式分为unarystreaming几种模式,拦截器也分为两种:unary interceptorsstreaming interceptors ,两种拦截器可以分别应用在服务端和客户端,所以gRPC总共为我们提供了四种拦截器。它们已经被定义成了go中的接口,我们创建的拦截器只要实现这些接口即可

gRPC四种拦截器一览

服务端拦截器

服务端的拦截器从请求开始按顺序执行拦截器,在执行完对应RPC的逻辑之后,再按反向的顺序执行拦截器中对响应的处理逻辑

服务端拦截器

unary interceptors

对于unary服务的拦截器只需实现UnaryServerInterceptor接口即可

func(ctx context.Context, req interface{}, 
     info *UnaryServerInfo, handler UnaryHandler)
 (resp interface{}, err error)

  • ctx context.Context:单个请求的上下文
  • req interface{}:RPC服务的请求结构体
  • info *UnaryServerInfo:RPC的服务信息
  • handler UnaryHandler:它包装了服务实现,通过调用它我们可以完成RPC并获取到响应

参数看不懂没关系,我们来看一个例子

示例

// 实现 unary interceptors
func orderUnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
 // Pre-processing logic
 s := time.Now()

 // Invoking the handler to complete the normal execution of a unary RPC.
 m, err := handler(ctx, req)

 // Post processing logic
 log.Printf("Method: %s, req: %s, resp: %s, latency: %s\n",
  info.FullMethod, req, m, time.Now().Sub(s))
  
 return m, err
}

func main() {
 s := grpc.NewServer(
    // 使用 unary interceptors
  grpc.UnaryInterceptor(orderUnaryServerInterceptor),
 )
 
  pb.RegisterOrderManagementServer(s, &OrderManagementImpl{})
  
 // ...
}

完整代码参考:https://github.com/liangwt/grpc-example/tree/main/06-interceptors/server。下同

假设我们的客户端请求了GetOrder,根据示例再重新看下拦截器接口的每一个参数

🌲 req interface{}

RPC服务的请求结构体,对于GetOrder来说就是orderId *wrapperspb.StringValue

🌲 info *UnaryServerInfo包含两个字段:

FullMethod是请求的method名字(例如/ecommerce.OrderManagement/getOrder);

Server就是服务实现(就是示例RegisterOrderManagementServer中的&OrderManagementImpl{})

🌲 handler包装了服务实现

所以在调用它之前我们可以进行改写reqctx、记录逻辑开始时间等操作

调用完handler即完成了RPC并获取到响应,我们不仅可以记录响应还可以改写响应

总结

这张图大致展示了UnaryServerInterceptor接口的每个参数的含义

streaming interceptors

对于stream服务的拦截器只要实现StreamServerInterceptor接口即可。它适用于我们上一篇介绍的

  • 服务器端流式 RPC
  • 客户端流式 RPC
  • 双向流式 RPC
func(srv interface{}, ss ServerStream, 
     info *StreamServerInfo, handler StreamHandler)
 error

  • srv interface{}:服务实现
  • ss ServerStream:服务端视角的流。怎么理解呢?无论是哪一种流式RPC对于服务端来说发送(SendMsg)就代表着响应数据,接收(RecvMsg)就代表着请求数据,不同的流式RPC的区别就在于是多次发送数据(服务器端流式 RPC)还是多次接收数据(客户端流式 RPC)或者两者均有(双向流式 RPC)。因此仅使用这一个抽象就代表了所有的流式RPC场景
  • info *StreamServerInfo:RPC的服务信息
  • handler StreamHandler:它包装了服务实现,通过调用它我们可以完成RPC

客户端拦截器

客户端拦截器和服务端拦截器类似,从请求开始按顺序执行拦截器,在获取到服务端响应之后,再按反向的顺序执行拦截器中对响应的处理逻辑

客户端拦截器

unary interceptors

client端要实现UnaryClientInterceptor接口实现的接口如下

func(ctx context.Context, method string, req, reply interface{}, 
     cc *ClientConn, invoker UnaryInvoker, opts ...CallOption)
 error

你可以在调用远程函数前拦截RPC,通过获取RPC相关信息,如参数,上下文,函数名,请求等,你甚至可以修改原始的远程调用

  • ctx context.Context:单个请求的上下文
  • method string:请求的method名字(例如/ecommerce.OrderManagement/getOrder)
  • req, reply interface{}:请求和响应数据
  • cc *ClientConn:客户端与服务端的链接
  • invoker UnaryInvoker:通过调用它我们可以完成RPC并获取到响应
  • opts ...CallOption:RPC调用的所有配置项,包含设置到conn上的,也包含配置在每一个调用上的

示例

func orderUnaryClientInterceptor(ctx context.Context, method string, req, reply interface{},
 cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption)
 error
 {
 // Pre-processor phase
 s := time.Now()

 // Invoking the remote method
 err := invoker(ctx, method, req, reply, cc, opts...)

 // Post-processor phase
 log.Printf("method: %s, req: %s, resp: %s, latency: %s\n",
  method, req, reply, time.Now().Sub(s))

 return err
}

func main() {
 conn, err := grpc.Dial("127.0.0.1:8009",
  grpc.WithInsecure(),
  grpc.WithUnaryInterceptor(orderUnaryClientInterceptor),
 )
 if err != nil {
  panic(err)
 }

 c := pb.NewOrderManagementClient(conn)
  
  // ...
}

根据示例再重新看下拦截器接口的参数

🌲 cc *grpc.ClientConn客户端与服务端的链接

这里的cc就是示例代码中c := pb.NewOrderManagementClient(conn)conn

🌲 invoker grpc.UnaryInvoker包装了服务实现

调用完invoker即完成了RPC,所以我们可以改写req或者在获取到reply之后修改响应

拦截器链

服务器只能配置一个 unary interceptor和一个 stream interceptor,否则会报错,客户端也是,虽然不会报错,但是只有最后一个才起作用。

// 服务端拦截器
s := grpc.NewServer(
  grpc.UnaryInterceptor(orderUnaryServerInterceptor),
  grpc.StreamInterceptor(orderStreamServerInterceptor),
)
// 客户端拦截器
conn, err := grpc.Dial("127.0.0.1:8009",
  grpc.WithInsecure(),
  grpc.WithUnaryInterceptor(orderUnaryClientInterceptor),
  grpc.WithStreamInterceptor(orderStreamClientInterceptor),
)

如果你想配置多个,可以使用拦截器链或者自己实现一个。

// 服务端拦截器
s := grpc.NewServer(
  grpc.ChainUnaryInterceptor(
    orderUnaryServerInterceptor1,
    orderUnaryServerInterceptor2,
  ),
  grpc.ChainStreamInterceptor(
    orderServerStreamInterceptor1,
    orderServerStreamInterceptor2,
  ),
)
// 客户端拦截器
conn, err := grpc.Dial("127.0.0.1:8009",
  grpc.WithInsecure(),
  grpc.WithChainUnaryInterceptor(
   orderUnaryClientInterceptor1,
      orderUnaryClientInterceptor2,
  ),
  grpc.WithChainStreamInterceptor(
   orderStreamClientInterceptor1,
      orderStreamClientInterceptor2,
  ),
)

生态

除了可以自己实现拦截器外,gRPC生态也提供了一系列的开源的拦截器可供使用,覆盖权限、日志、监控等诸多方面

https://github.com/grpc-ecosystem/go-grpc-middleware

本篇为【写给go开发者的gRPC教程】系列第三篇

第一篇:protobuf基础

第二篇:通信模式

最后

最后在结尾推荐15个 拿来即用的在线画图模版,涵盖需求分析、技术方案评审、述职汇报和甲方商务对接的支持,这些我们经常会接触到的工作。

这些都是我的专栏--程序员的全能画图课,完结后的加更内容,现在订阅专栏后马上就能获取这些实用的在线画图模版。

专栏中有针对大型项目的技术评审案例分析和带练演示,无论是小型项目和大型项目都通过实际的案例分析讲解带你掌握下面这些技能。

课程采用理解优于记忆的方式,用平时开发中常见的案例分析,教会大家对他们的使用。同时还会普及一些做好业务开发的经验要诀,教大家怎么对业务结构和流程进行多视角的分析和可视化表达。

现在可在公众号专栏《程序员的全能画图课》上直接订阅或者扫描上方方海报二维码订阅,苹果手机用户建议扫码订阅,不用交苹果税。

网管叨bi叨
分享软件开发和系统架构设计基础、Go 语言和Kubernetes。
 最新文章