拦截器Interceptor
拦截器Interceptor
一、概述
想在每个 RPC 方法的前或后做某些事情,怎么做?
gRPC 提供了 Interceptor 功能,包括客户端拦截器和服务端拦截器。可以在接收到请求或者发起请求之前优先对请求中的数据做一些处理后再转交给指定的服务处理并响应,很适合在这里处理验证、日志等流程。
gRPC-go 在 v1.28.0版本增加了多 interceptor 支持,可以在不借助第三方库(go-grpc-middleware)的情况下添加多个 interceptor 了。
go-grpc-middleware 中也提供了多种常用 interceptor ,可以直接使用。
在 gRPC 中,根据拦截的方法类型不同可以分为拦截 Unary 方法的一元拦截器,和作用于 Stream 方法的流拦截器。
在 gRPC 中,大类可分为两种 RPC 方法,与拦截器的对应关系是:
- 普通方法:一元拦截器(grpc.UnaryInterceptor)
- 流方法:流拦截器(grpc.StreamInterceptor)
同时还分为服务端拦截器和客户端拦截器,所以一共有以下4种类型:
- grpc.UnaryServerInterceptor
- grpc.StreamServerInterceptor
- grpc.UnaryClientInterceptor
- grpc.StreamClientInterceptor
二、客户端拦截器
使用客户端拦截器 只需要在 Dial的时候指定相应的 DialOption 即可。
Unary Interceptor
客户端一元拦截器类型为 grpc.UnaryClientInterceptor
,具体如下:
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
可以看到,所谓的拦截器其实就是一个函数,可以分为预处理(pre-processing)
、调用RPC方法(invoking RPC method)
、后处理(post-processing)
三个阶段。
参数含义如下:
- ctx:Go语言中的上下文,一般和 Goroutine 配合使用,起到超时控制的效果
- method:当前调用的 RPC 方法名
- req:本次请求的参数,只有在
处理前
阶段修改才有效 - reply:本次请求响应,需要在
处理后
阶段才能获取到 - cc:gRPC 连接信息
- invoker:可以看做是当前 RPC 方法,一般在拦截器中调用 invoker 能达到调用 RPC 方法的效果,当然底层也是 gRPC 在处理。
- opts:本次调用指定的 options 信息
作为一个客户端拦截器,可以在处理前
检查 req 看看本次请求带没带 token 之类的鉴权数据,没有的话就可以在拦截器中加上。
Stream Interceptor
type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)
由于 StreamAPI 和 UnaryAPI有所不同,因此拦截器方面也有所区别,比如 req 参数变成了 streamer 。同时其拦截过程也有所不同,不在是处理 req resp,而是对 streamer 这个流对象进行包装,比如说实现自己的 SendMsg 和 RecvMsg 方法。
然后在这些方法中的预处理(pre-processing)
、调用RPC方法(invoking RPC method)
、后处理(post-processing)
各个阶段加入自己的逻辑。
三、服务端拦截器
服务端拦截器和客户端拦截器类似,就不做过多描述。使用客户端拦截器 只需要在 NewServer 的时候指定相应的 ServerOption 即可。
Unary Interceptor
定义如下:
type 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 方法真正执行的逻辑
Stream Interceptor
type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error
四、UnaryInterceptor
一元拦截器可以分为3个阶段:
- 1)预处理(pre-processing)
- 2)调用RPC方法(invoking RPC method)
- 3)后处理(post-processing)
proto
syntax = "proto3";// 协议为proto3
//option go_package = "path;name";
//path 表示生成的go文件的存放地址,会自动生成目录的。
//name 表示生成的go文件所属的包名
// 生成pb.go命令: protoc -I ./ --go_out=plugins=grpc:.\13unaryInterceptor\proto\ .\13unaryInterceptor\proto\simple.proto
option go_package = "./;proto";
package proto;
// 定义我们的服务(可定义多个服务,每个服务可定义多个接口)
service Simple{
rpc Route (SimpleRequest) returns (SimpleResponse){};
}
// 定义发送请求信息
message SimpleRequest{
// 定义发送的参数,采用驼峰命名方式,小写加下划线,如:student_name
// 参数类型 参数名 标识号(不可重复)
string data = 1;
}
// 定义响应信息
message SimpleResponse{
// 定义接收的参数
// 参数类型 参数名 标识号(不可重复)
int32 code = 1;
string value = 2;
}
编译:
go-grpc-example> protoc -I ./ --go_out=plugins=grpc:.\13unaryInterceptor\proto\ .\13unaryInterceptor\proto\simple.proto
Client
package main
import (
"context"
pb "go-grpc-example/13unaryInterceptor/proto"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
/*
@author RandySun
@create 2022-05-09-21:23
*/
const (
// Address 监听地址
Address string = ":8001"
// NetWork 网络通信协议
NetWork string = "tcp"
)
// unaryInterceptor 一个简单的 unary interceptor 示例。
func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// pre-processing var interceptor grpc.UnaryClientInterceptor
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...) // invoking RPC method 调用 RPC 方法
// post-processing
end := time.Now()
log.Fatalf("RPC: %s, req:%v start time: %s, end time: %s, err: %v", method, req, start.Format(time.RFC3339), end.Format(time.RFC3339), err)
return err
}
func main() {
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(unaryInterceptor))
if err != nil {
log.Fatalf("net.Connect connect: %v", err)
}
defer conn.Close()
// 建立gRpc连接
grpcClient := pb.NewSimpleClient(conn)
// 创建发送结构体
req := pb.SimpleRequest{
Data: "grpc",
}
// 调用 Route 方法 同时传入context.Context, 在有需要时可以让我们改变RPC的行为,比如超时/取消一个正在运行的RPC
res, err := grpcClient.Route(context.Background(), &req)
if err != nil {
log.Fatalf("Call Route err:%v", err)
}
// 打印返回直
log.Println("服务的返回响应data:", res)
}
invoker(ctx, method, req, reply, cc, opts...)
是真正调用 RPC 方法。因此我们可以在调用前后增加自己的逻辑:比如调用前检查一下参数之类的,调用后记录一下本次请求处理耗时等。
建立连接时通过 grpc.WithUnaryInterceptor 指定要加载的拦截器即可。
Server
服务端的一元拦截器和客户端类似:
package main
import (
"context"
pb "go-grpc-example/13unaryInterceptor/proto"
"log"
"net"
"time"
"google.golang.org/grpc"
)
/*
@author RandySun
@create 2022-05-09-21:23
*/
// SimpleService 定义我们的服务
type SimpleService struct {
}
// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
res := pb.SimpleResponse{
Code: 200,
Value: "hello " + req.Data,
}
return &res, nil
}
func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
m, err := handler(ctx, req)
end := time.Now()
// 记录请求参数 耗时 错误信息等数据
log.Fatalf("RPC: %s,req:%v start time: %s, end time: %s, err: %v", info.FullMethod, req, start.Format(time.RFC3339), end.Format(time.RFC3339), err)
return m, err
}
const (
// Address 监听地址
Address string = ":8001"
// NetWork 网络通信协议
NetWork string = "tcp"
)
func main() {
// 监听本地端口
listener, err := net.Listen(NetWork, Address)
if err != nil {
log.Fatalf("net.Listen err: %V", err)
}
log.Println(Address, "net.Listing...")
// 创建grpc服务实例
grpcServer := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor))
// 在grpc服务器注册我们的服务
pb.RegisterSimpleServer(grpcServer, &SimpleService{})
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("grpcService.Serve err:%v", err)
}
log.Println("grpcService.Serve run succ")
}
服务端则是在 NewServer 时指定拦截器:
Test
Server
go build .\service.go
.\service.exe
2022/04/09 20:59:19 :8001 net.Listing...
2022/04/09 20:59:26 RPC: /proto.Simple/Route,req:data:"grpc" start time: 2022-04-09T20:59:26+08:00, end time: 2022-04-09T20:59:26+08:00, err: <nil>
Client
go build .\client.go
.\client.exe
2022/04/09 20:59:26 RPC: /proto.Simple/Route, req:data:"grpc" start time: 2022-04-09T20:59:26+08:00, end time: 2022-04-09T20:59:26+08:00, err: rpc error: code = Unavailable desc = error reading from server: read tcp 127.
0.0.1:58879->127.0.0.1:8001: wsarecv: An existing connection was forcibly closed by the remote host.
五、StreamInterceptor
流拦截器过程和一元拦截器有所不同,同样可以分为3个阶段:
- 1)预处理(pre-processing)
- 2)调用RPC方法(invoking RPC method)
- 3)后处理(post-processing)
预处理阶段和一元拦截器类似,但是调用RPC方法和后处理这两个阶段则完全不同。
StreamAPI 的请求和响应都是通过 Stream 进行传递的,更进一步是通过 Streamer 调用 SendMsg 和 RecvMsg 这两个方法获取的。
然后 Streamer 又是调用RPC方法来获取的,所以在流拦截器中我们可以对 Streamer 进行包装,然后实现 SendMsg 和 RecvMsg 这两个方法。
proto
syntax = "proto3";// 协议为proto3
package proto;
//option go_package = "grpc/03serverStream/proto";
option go_package = "./;proto";
// protoc -I ./ --go_out=plugins=grpc:.\14streamInterceptor\proto\ .\14streamInterceptor\proto\bothStream.proto
// 定义我们的服务(可定义多个服务,每个服务可定义多个接口)
service Stream{
// 双向流式rpc,同时在请求参数前和响应参数前加上stream
rpc Conversations(stream StreamRequest) returns(stream StreamResponse){};
}
// 定义发送请求信息
message StreamRequest{
//流请求参数
string question = 1;
}
// 定义流式响应信息
message StreamResponse{
//流响应数据
string answer = 1;
}
编译:
go-grpc-example> protoc -I ./ --go_out=plugins=grpc:.\14streamInterceptor\proto\ .\14streamInterceptor\proto\bothStream.proto
Client
本例中通过结构体嵌入的方式,对 Streamer 进行包装,在 SendMsg 和 RecvMsg 之前打印出具体的值。
package main
import (
"context"
"fmt"
pb "go-grpc-example/14streamInterceptor/proto"
"io"
"log"
"strconv"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
/*
@author RandySun
@create 2022-05-09-21:43
*/
// Address 连接地址
const Address string = ":8000"
var streamClient pb.StreamClient
// conversations 调用服务端的Conversations方法
func conversations() {
//调用服务端的Conversations方法,获取流
stream, err := streamClient.Conversations(context.Background())
if err != nil {
log.Fatalf("get conversations stream err: %v", err)
}
for n := 0; n < 5; n++ {
fmt.Println(12223)
err := stream.Send(&pb.StreamRequest{Question: "stream client rpc " + strconv.Itoa(n)})
if err != nil {
log.Fatalf("stream request err: %v", err)
}
// 接收服务端消息
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("Conversations get stream err: %v", err)
}
// 打印返回值
log.Println(res.Answer)
}
//最后关闭流
err = stream.CloseSend()
if err != nil {
log.Fatalf("Conversations close stream err: %v", err)
}
}
// wrappedStream 用于包装 grpc.ClientStream 结构体并拦截其对应的方法。
type wrappedStream struct {
grpc.ClientStream
}
func newWrappedStream(s grpc.ClientStream) grpc.ClientStream {
return &wrappedStream{s}
}
func (w *wrappedStream) RecvMsg(m interface{}) error {
log.Fatalf("Receive a message2 (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ClientStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m interface{}) error {
log.Fatalf("Send a message1 (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ClientStream.SendMsg(m)
}
// streamInterceptor 一个简单的 stream interceptor 示例。
func streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
s, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
return nil, err
}
log.Fatalf("一个简单的 client stream interceptor 示例")
return newWrappedStream(s), nil
}
func main() {
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStreamInterceptor(streamInterceptor))
if err != nil {
log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()
// 建立gRPC连接
streamClient = pb.NewStreamClient(conn)
conversations()
}
连接时则通过 grpc.WithStreamInterceptor 指定要加载的拦截器。
Server
和客户端类似。
相似的,通过 函数指定要加载的拦截器。
package main
import (
"fmt"
pb "go-grpc-example/14streamInterceptor/proto"
"io"
"log"
"net"
"strconv"
"time"
"google.golang.org/grpc"
)
/*
@author RandySun
@create 2022-05-09-21:43
*/
// StreamService 定义我们的服务
type StreamService struct{}
// Conversations 实现Conversations方法
func (s *StreamService) Conversations(srv pb.Stream_ConversationsServer) error {
n := 1
for {
fmt.Println(1111)
req, err := srv.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
err = srv.Send(&pb.StreamResponse{
Answer: "from stream server answer: the " + strconv.Itoa(n) + "question is " + req.Question,
})
if err != nil {
return err
}
n++
log.Printf("from stream client question: %s", req.Question)
}
}
const (
// Address 监听地址
Address string = ":8000"
// Network 网络通信协议
Network string = "tcp"
)
type wrappedStream struct {
grpc.ServerStream
}
func newWrappedStream(s grpc.ServerStream) grpc.ServerStream {
return &wrappedStream{s}
}
func (w *wrappedStream) RecvMsg(m interface{}) error {
log.Fatalf("Receive a message (Type: %T) at %s", m, time.Now().Format(time.RFC3339))
return w.ServerStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m interface{}) error {
log.Fatalf("Send a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ServerStream.SendMsg(m)
}
func streamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
// 包装 grpc.ServerStream 以替换 RecvMsg SendMsg这两个方法。
err := handler(srv, newWrappedStream(ss))
if err != nil {
log.Fatalf("RPC failed with error %v", err)
}
log.Fatalf("一个简单的 server stream interceptor 示例")
return err
}
func main() {
// 监听本地端口
listener, err := net.Listen(Network, Address)
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
log.Println(Address + " net.Listing...")
// 新建gRPC服务器实例
grpcServer := grpc.NewServer(grpc.StreamInterceptor(streamInterceptor))
// 在gRPC服务器注册我们的服务
pb.RegisterStreamServer(grpcServer, &StreamService{})
//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("grpcServer.Serve err: %v", err)
}
}
五、如何实现多个拦截器
另外,可以发现 gRPC 本身居然只能设置一个拦截器,难道所有的逻辑都只能写在一起?
关于这一点,你可以放心。采用开源项目 go-grpc-middleware 就可以解决这个问题,本章也会使用它。
import "github.com/grpc-ecosystem/go-grpc-middleware"
myServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
...
)),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
...
)),
)
编写 gRPC interceptor 的代码,我们会将实现以下拦截器:
- logging:RPC 方法的入参出参的日志输出
- recover:RPC 方法的异常保护和日志输出
proto:
syntax = "proto3";// 协议为proto3
//option go_package = "path;name";
//path 表示生成的go文件的存放地址,会自动生成目录的。
//name 表示生成的go文件所属的包名
// protoc -I ./ --go_out=plugins=grpc:.\15manyInterceptor\proto\ .\15manyInterceptor\proto\simple.proto
option go_package = "./;proto";
package proto;
// 定义我们的服务(可定义多个服务,每个服务可定义多个接口)
service Simple{
rpc Route (SimpleRequest) returns (SimpleResponse){};
}
// 定义发送请求信息
message SimpleRequest{
// 定义发送的参数,采用驼峰命名方式,小写加下划线,如:student_name
// 参数类型 参数名 标识号(不可重复)
string data = 1;
}
// 定义响应信息
message SimpleResponse{
// 定义接收的参数
// 参数类型 参数名 标识号(不可重复)
int32 code = 1;
string value = 2;
}
编译:
go-grpc-example> protoc -I ./ --go_out=plugins=grpc:.\15manyInterceptor\proto\ .\15manyInterceptor\proto\simple.proto
实现client
package main
import (
"context"
"fmt"
pb "go-grpc-example/15manyInterceptor/proto"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
/*
@author RandySun
@create 2022-05-09-21:53
*/
const (
// Address 监听地址
Address string = ":8001"
// NetWork 网络通信协议
NetWork string = "tcp"
)
// unaryInterceptor 一个简单的 unary interceptor 示例。
func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// pre-processing var interceptor grpc.UnaryClientInterceptor
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...) // invoking RPC method 调用 RPC 方法
// post-processing
end := time.Now()
log.Fatalf("RPC: %s, req:%v start time: %s, end time: %s, err: %v", method, req, start.Format(time.RFC3339), end.Format(time.RFC3339), err)
return err
}
func main() {
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(unaryInterceptor))
if err != nil {
log.Fatalf("net.Connect connect: %v", err)
}
defer conn.Close()
// 建立gRpc连接
grpcClient := pb.NewSimpleClient(conn)
// 创建发送结构体
req := pb.SimpleRequest{
Data: "grpc",
}
// 调用 Route 方法 同时传入context.Context, 在有需要时可以让我们改变RPC的行为,比如超时/取消一个正在运行的RPC
res, err := grpcClient.Route(context.Background(), &req)
if err != nil {
log.Fatalf("Call Route err:%v", err)
}
fmt.Println(res, 44444)
// 打印返回直
log.Println("服务的返回响应data:", res)
}
实现 server interceptor
logging
func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("gRPC method: %s, %v", info.FullMethod, req)
resp, err := handler(ctx, req)
log.Printf("gRPC method: %s, %v", info.FullMethod, resp)
return resp, err
}
recover
func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
defer func() {
if e := recover(); e != nil {
debug.PrintStack()
err = status.Errorf(codes.Internal, "Panic err: %v", e)
}
}()
return handler(ctx, req)
}
package main
import (
"context"
pb "go-grpc-example/15manyInterceptor/proto"
"log"
"net"
"runtime/debug"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
/*
@author RandySun
@create 2022-05-09-21:53
*/
// SimpleService 定义我们的服务
type SimpleService struct {
}
// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
res := pb.SimpleResponse{
Code: 200,
Value: "hello " + req.Data,
}
return &res, nil
}
// LoggingInterceptor RPC 方法的入参出参的日志输出
func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("gRPC method: %s, %v", info.FullMethod, req)
resp, err := handler(ctx, req)
log.Printf("gRPC method: %s, %v", info.FullMethod, resp)
return resp, err
}
// RecoveryInterceptor RPC 方法的异常保护和日志输出
func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
//RPC 方法的异常保护和日志输出
defer func() {
if e := recover(); e != nil {
debug.PrintStack()
err = status.Errorf(codes.Internal, "Panic err: %v", e)
}
}()
return handler(ctx, req)
}
const (
// Address 监听地址
Address string = ":8001"
// NetWork 网络通信协议
NetWork string = "tcp"
)
func main() {
// 监听本地端口
listener, err := net.Listen(NetWork, Address)
if err != nil {
log.Fatalf("net.Listen err: %V", err)
}
log.Println(Address, "net.Listing...")
// 创建grpc服务实例
opts := []grpc.ServerOption{
grpc_middleware.WithUnaryServerChain(
RecoveryInterceptor,
LoggingInterceptor,
),
}
grpcServer := grpc.NewServer(opts...)
// 在grpc服务器注册我们的服务
pb.RegisterSimpleServer(grpcServer, &SimpleService{})
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("grpcService.Serve err:%v", err)
}
log.Println("grpcService.Serve run succ")
}
logging
启动 server.go,执行 client.go 发起请求,得到结果:
$ go run server.go
2022/04/09 21:34:14 :8001 net.Listing...
2022/04/09 21:34:21 gRPC method: /proto.Simple/Route, data:"grpc"
2022/04/09 21:34:21 gRPC method: /proto.Simple/Route, code:200 value:"hello grpc"
recover
在 RPC 方法中人为地制造运行时错误,再重复启动 server/client.go,得到结果:
client
$ go run client.go
code:200 value:"hello grpc" 44444
2022/04/09 21:44:36 服务的返回响应data: code:200 value:"hello grpc"
server
$ go run server.go
goroutine 23 [running]:
runtime/debug.Stack(0xc420223588, 0x1033da9, 0xc420001980)
/usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:24 +0xa7
runtime/debug.PrintStack()
/usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:16 +0x22
main.RecoveryInterceptor.func1(0xc420223a10)
...
检查服务是否仍然运行,即可知道 Recovery 是否成功生效
六、小结
1)拦截器分类与定义 gRPC 拦截器可以分为:一元拦截器和流拦截器,服务端拦截器和客户端拦截器。
一共有以下4种类型:
- grpc.UnaryServerInterceptor
- grpc.StreamServerInterceptor
- grpc.UnaryClientInterceptor
- grpc.StreamClientInterceptor
拦截器本质上就是一个特定类型的函数,所以实现拦截器只需要实现对应类型方法(方法签名相同)即可。
2)拦截器执行过程
一元拦截器
- 1)预处理
- 2)调用RPC方法
- 3)后处理
流拦截器
- 1)预处理
- 2)调用RPC方法 获取 Streamer
- 3)后处理
- 调用 SendMsg 、RecvMsg 之前
- 调用 SendMsg 、RecvMsg
- 调用 SendMsg 、RecvMsg 之后
3)拦截器使用及执行顺序
配置多个拦截器时,会按照参数传入顺序依次执行
所以,如果想配置一个 Recovery 拦截器则必须放在第一个,放在最后则无法捕获前面执行的拦截器中触发的 panic。
同时也可以将 一元和流拦截器一起配置,gRPC 会根据不同方法选择对应类型的拦截器执行。