RPC 本身只能设置一个拦截器, 采用开源项目 go-grpc-middleware 就可以解决这个问题
go-grpc-middleware 对interceptor进行了封装,支持多个拦截器的链式组装,对于需要多种处理的地方使用起来会更方便些。
官方使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import "github.com/grpc-ecosystem/go-grpc-middleware"
myServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_ctxtags.StreamServerInterceptor(),
grpc_opentracing.StreamServerInterceptor(),
grpc_prometheus.StreamServerInterceptor,
grpc_zap.StreamServerInterceptor(zapLogger),
grpc_auth.StreamServerInterceptor(myAuthFunction),
grpc_recovery.StreamServerInterceptor(),
)),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_ctxtags.UnaryServerInterceptor(),
grpc_opentracing.UnaryServerInterceptor(),
grpc_prometheus.UnaryServerInterceptor,
grpc_zap.UnaryServerInterceptor(zapLogger),
grpc_auth.UnaryServerInterceptor(myAuthFunction),
grpc_recovery.UnaryServerInterceptor(),
)),
)
实现拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//log
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
}
func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
var err = auth(ctx)
if err != nil {
log.Printf("auth fail: %v", err)
return nil, err
}
return handler(ctx, req)
}
//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)
}
以上代码中实现了三个拦截器,作用分别为记录日志、身份验证、异常捕获。
测试验证
LoggingInterceptor
1
2
3
4
go run server.go
go run client.go
2019/07/08 00:13:10 gRPC method: /hello.HelloService/SayHello, greeting:"gRPC"
2019/07/08 00:13:10 gRPC method: /hello.HelloService/SayHello, reply:"Hello gRPC."
AuthInterceptor
将client.go中 代码中的appkey 改为一个错误的值 mysecret1, 再次执行client.go
1
2
3
go run client.go
2019/07/08 00:17:29 rpc error: code = Unauthenticated desc = Token认证信息无效: appid=10001, appkey=mysecret1
exit status 1
RecoveryInterceptor
在 RPC 方法中人为地制造运行时错误,我这里在server.go 中加入了 1/0。 再重新启动 server.go, client.go,得到结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go run client.go
2019/07/08 00:20:54 rpc error: code = Internal desc = Panic err: runtime error: integer divide by zero
exit status 1
//server.go
2019/07/08 00:20:54 gRPC method: /hello.HelloService/SayHello, greeting:"gRPC"
goroutine 50 [running]:
runtime/debug.Stack(0x0, 0xc000086000, 0x19b96d0)
/usr/local/Cellar/go/1.12.4/libexec/src/runtime/debug/stack.go:24 +0x9d
runtime/debug.PrintStack()
/usr/local/Cellar/go/1.12.4/libexec/src/runtime/debug/stack.go:16 +0x22
main.RecoveryInterceptor.func1(0xc0001e9b08)
/workspace/go/src/grpc/grpc_middleware/server.go:82 +0x57
panic(0x1468b00, 0x18b7620)
/usr/local/Cellar/go/1.12.4/libexec/src/runtime/panic.go:522 +0x1b5
main.helloService.SayHello(...)
检查服务是否仍然运行,即可知道 Recovery 是否成功生效。
示例代码: grpc_middleware