gRPC
的拦截器和其他框架的拦截器(也称middleware)作用是一样的。利用拦截器我们可以在不侵入业务逻辑的前提下修改或者记录服务端或客户端的请求与响应,利用拦截器我们可以实现诸如日志记录、权限认证、限流等诸多功能
上一篇提到gRPC
的通信模式分为unary
和streaming
几种模式,拦截器也分为两种:unary interceptors
和streaming interceptors
,两种拦截器可以分别应用在服务端和客户端,所以gRPC总共为我们提供了四种拦截器。它们已经被定义成了go中的接口,我们创建的拦截器只要实现这些接口即可
服务端拦截器
服务端的拦截器从请求开始按顺序执行拦截器,在执行完对应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
包装了服务实现
所以在调用它之前我们可以进行改写req
或ctx
、记录逻辑开始时间等操作
调用完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个 拿来即用的在线画图模版,涵盖需求分析、技术方案评审、述职汇报和甲方商务对接的支持,这些我们经常会接触到的工作。
这些都是我的专栏--程序员的全能画图课,完结后的加更内容,现在订阅专栏后马上就能获取这些实用的在线画图模版。
专栏中有针对大型项目的技术评审案例分析和带练演示,无论是小型项目和大型项目都通过实际的案例分析讲解带你掌握下面这些技能。
课程采用理解优于记忆的方式,用平时开发中常见的案例分析,教会大家对他们的使用。同时还会普及一些做好业务开发的经验要诀,教大家怎么对业务结构和流程进行多视角的分析和可视化表达。
现在可在公众号专栏《程序员的全能画图课》上直接订阅或者扫描上方方海报二维码订阅,苹果手机用户建议扫码订阅,不用交苹果税。