gRPC middleware

gRPC middleware

Posted by alovn on July 7, 2019

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