前提阅读:
本文的代码,可能在以上代码的基础上进行了更改。
前言
什么是拦截器?
拦截器(Interceptor),主要完成请求参数的解析、将页面表单参数赋给值栈中相应属性、执行功能检验、程序异常调试等工作。
说人话,就是,我需要在调用 gRPC
方法之前(或之后),对某些参数(如,日志、异常,甚至是token)做一些处理。
实现这类功能的活,就叫“拦截器”。
gRPC中的拦截器
gRPC
中,分别对普通方法和流方法提供了截取器的支持,也就是:
- 一元拦截器:
grpc.UnaryInterceptor
- 流拦截器:
grpc.StreamInterceptor
注意:但是,gRPC框架中只能为每个服务设置一个截取器,因此所有的截取工作只能在一个函数中完成。要想为每个服务设置多个不同的拦截器也可以,需要拉取一个开源的依赖包:
go-grpc-middleware
。在开源的grpc-ecosystem项目中的go-grpc-middleware包已经基于gRPC对截取器实现了链式截取器的支持。
解析
本文重点来看一元拦截器,其实流拦截器也是类似的。grpc.UnaryInterceptor
中的结构如下所示
func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {
return func(o *options) {
if o.unaryInt != nil {
panic("The unary server interceptor was already set and may not be reset.")
}
o.unaryInt = i
}
}
其中,UnaryServerInterceptor
的接口类似如下所示:
要实现普通方法的截取器(一元拦截器),就需要为 grpc.UnaryInterceptor
的参数,即UnaryServerInterceptor
实现一个函数。
例子:
func myLogFilter(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
log.Printf("gRPC method: %s, %v", info.FullMethod, req)
resp, err = handler(ctx, req)
log.Println("resp is ", resp)
return resp, err
}
以上,简单实现了一个拦截器。
解释:
- ctx context.Context:请求上下文
- req interface{}:RPC 方法的请求参数
- info *UnaryServerInfo:RPC 方法的所有信息,表示当前是对应的那个gRPC方法
- handler UnaryHandler:RPC 方法本身,表示对应当前的gRPC方法函数
-----------参考:带入gRPC:Unary and Stream interceptor
使用:
要使用filter截取器函数,只需要在启动gRPC服务时作为参数输入即可:
server := grpc.NewServer(grpc.UnaryInterceptor(myLogFilter))
实现
代码修改
为了代码不用太多的改动,本文在server.go
端进行了一定代码修改:
package main
......
// 日志和异常拦截
func myLogFilter(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// 输出日志
log.Printf("gRPC method: %s, %v", info.FullMethod, req)
resp, err = handler(ctx, req)
// 拦截异常
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
log.Println("resp is ", resp)
return resp, err
}
func main() {
// 引入证书
certificate, err := tls.LoadX509KeyPair("server.pem", "server.key")
......
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{certificate},
ClientAuth: tls.RequireAndVerifyClientCert, // NOTE: this is optional!
ClientCAs: certPool,
})
// 初始化一个gRPC的结构体对象
// 开启证书,并传入拦截器
s := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(myLogFilter))
// 注册服务
pb.RegisterGreeterServer(s, &server{})
......
}
实验
服务端启动:
go run server.go
客户端启动:
go run client.go
结果:
多拦截器
注意:但是,gRPC框架中只能为每个服务设置一个截取器,因此所有的截取工作只能在一个函数中完成。要想为每个服务设置多个不同的拦截器也可以,需要拉取一个开源的依赖包:
go-grpc-middleware
。在开源的grpc-ecosystem项目中的go-grpc-middleware包已经基于gRPC对截取器实现了链式截取器的支持。
以下是go-grpc-middleware包中链式截取器的简单用法
import "github.com/grpc-ecosystem/go-grpc-middleware"
myServer := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
filter1, filter2, ...
)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
filter1, filter2, ...
)),
)
----------参考:Go语言高级编程 · 4.5 gRPC进阶
总结
本文主要介绍了gRPC中的拦截器的概念、作用和简单的用法。
具体的深入实践还需要再实际生产过程中进行摸索。