1.拦截器是什么?
gRPC拦截器(interceptor)是一种函数,它可以在gRPC调用之前和之后执行一些逻辑,例如认证、授权、日志记录、监控和统计等。拦截器函数是gRPC中非常重要的概念,它允许我们在服务端和客户端添加自定义逻辑,以满足业务需求和运维需求。
在gRPC中,拦截器函数通常通过实现grpc.UnaryServerInterceptor和grpc.StreamServerInterceptor接口来定义。UnaryServerInterceptor用于拦截一元RPC请求,而StreamServerInterceptor用于拦截流式RPC请求。在客户端中,我们可以使用grpc.UnaryClientInterceptor和grpc.StreamClientInterceptor来拦截gRPC调用。
在gRPC中,拦截器函数可以被链接起来,形成一个拦截器链。在这个拦截器链中,每个拦截器函数都可以处理请求并将其转发给下一个拦截器函数,或者直接返回响应。因此,我们可以在拦截器函数中编写不同的逻辑,例如实现认证、授权、监控和统计等。
以下是一些常见的gRPC拦截器:
- 认证和授权拦截器:用于对gRPC调用进行身份验证和权限控制,例如检查token、验证用户名和密码、检查访问控制列表等;
- 日志记录拦截器:用于记录gRPC调用的日志,例如记录请求的方法、参数、响应状态等;
- 监控和统计拦截器:用于监控gRPC调用的性能和吞吐量,例如记录调用次数、响应时间、错误率等;
- 缓存拦截器:用于在服务端或客户端缓存一些数据,例如缓存计算结果、缓存数据库查询结果等。
开源的gRPC拦截器库:
2.客户端拦截器
在gRPC客户端中,可以使用拦截器来记录日志,客户端的拦截器可以在每个gRPC调用前后执行一些逻辑,例如记录请求和响应的信息。
下面是一个示例,说明如何使用拦截器来记录gRPC调用的日志:
注意,这里使用的是grpc.WithUnaryInterceptor()
,与服务器的拦截器方法不同。可以使用ctrl 按住方法直接复制要写的 UnaryInterceptor
函数体
import (
"context"
"log"
"google.golang.org/grpc"
)
func UnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
log.Printf("method=%s, request=%+v", method, req)
err := invoker(ctx, method, req, reply, cc, opts...)
if err != nil {
log.Printf("method=%s, error=%v", method, err)
} else {
log.Printf("method=%s, response=%+v", method, reply)
}
return err
}
func main() {
// 创建一个grpc连接,拨号时添加注册拦截器
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithUnaryInterceptor(UnaryInterceptor),
)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
// 创建一个客户端
client := pb.NewMyServiceClient(conn)
// 发起一个gRPC调用
resp, err := client.MyMethod(context.Background(), &pb.MyRequest{})
if err != nil {
log.Fatalf("failed to call MyMethod: %v", err)
}
log.Printf("response=%+v", resp)
}
客户端打印结果:
3.服务端拦截器
import (
"context"
"log"
"google.golang.org/grpc"
)
func UnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("method=%s, request=%+v", info.FullMethod, req)
resp, err := handler(ctx, req)
if err != nil {
log.Printf("method=%s, error=%v", info.FullMethod, err)
} else {
log.Printf("method=%s, response=%+v", info.FullMethod, resp)
}
return resp, err
}
func main() {
// 创建一个grpc服务器
s := grpc.NewServer(
grpc.UnaryInterceptor(UnaryInterceptor),
)
// 注册服务
pb.RegisterMyServiceServer(s, &server{})
// 启动服务器
s.Serve(listener)
}
服务端打印结果:
4.拦截器设置顺序
在gRPC中,拦截器的优先级取决于它们被添加到拦截器链中的顺序。在ChainUnaryInterceptor
方法中,第一个拦截器函数将是最外层的函数,而最后一个拦截器函数将是最内层的函数。因此,如果我们想要设置拦截器的优先级,可以通过调整它们的添加顺序来实现。
以下是一个示例,说明如何为两个拦截器函数设置优先级:
import (
"context"
"log"
"google.golang.org/grpc"
)
func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("logging interceptor: method=%s, request=%+v", info.FullMethod, req)
resp, err := handler(ctx, req)
if err != nil {
log.Printf("logging interceptor: method=%s, error=%v", info.FullMethod, err)
} else {
log.Printf("logging interceptor: method=%s, response=%+v", info.FullMethod, resp)
}
return resp, err
}
func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("auth interceptor: method=%s, request=%+v", info.FullMethod, req)
resp, err := handler(ctx, req)
if err != nil {
log.Printf("auth interceptor: method=%s, error=%v", info.FullMethod, err)
} else {
log.Printf("auth interceptor: method=%s, response=%+v", info.FullMethod, resp)
}
return resp, err
}
func main() {
// 创建一个grpc服务器
s := grpc.NewServer(
grpc.ChainUnaryInterceptor(
LoggingInterceptor,
AuthInterceptor,
),
)
// 注册服务
pb.RegisterMyServiceServer(s, &server{})
// 启动服务器
s.Serve(listener)
}
在这个例子中,我们为gRPC服务器添加了两个拦截器函数:LoggingInterceptor
和AuthInterceptor
。在拦截器链中,LoggingInterceptor
将会是最外层的函数,而AuthInterceptor
将会是最内层的函数。这意味着LoggingInterceptor
将首先被调用,然后再调用AuthInterceptor
。
如果我们想要改变它们的顺序,例如将AuthInterceptor
放在LoggingInterceptor
之前,可以通过调整拦截器的添加顺序来实现。例如,将ChainUnaryInterceptor
方法中的拦截器函数列表修改为:
grpc.ChainUnaryInterceptor(
AuthInterceptor,
LoggingInterceptor,
)
这样,AuthInterceptor
将会是最外层的函数,而LoggingInterceptor
将会是最内层的函数,这样我们就为拦截器函数设置了优先级。