grpc的中间件以及中间件库有很多,go-grpc-middleware应该是其中应用最广泛,本文主要介绍其中的grpc_zap、grpc_auth和grpc_recovery中间件。

go-grpc-middleware简介

go-grpc-middleware封装了认证(auth), 日志( logging), 消息(message), 验证(validation), 重试(retries) 和监控(retries)等拦截器。

  • 安装 go get github.com/grpc-ecosystem/go-grpc-middleware
  • 使用
    1. import "github.com/grpc-ecosystem/go-grpc-middleware"
    2. myServer := grpc.NewServer(
    3. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
    4. grpc_ctxtags.StreamServerInterceptor(),
    5. grpc_opentracing.StreamServerInterceptor(),
    6. grpc_prometheus.StreamServerInterceptor,
    7. grpc_zap.StreamServerInterceptor(zapLogger),
    8. grpc_auth.StreamServerInterceptor(myAuthFunction),
    9. grpc_recovery.StreamServerInterceptor(),
    10. )),
    11. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
    12. grpc_ctxtags.UnaryServerInterceptor(),
    13. grpc_opentracing.UnaryServerInterceptor(),
    14. grpc_prometheus.UnaryServerInterceptor,
    15. grpc_zap.UnaryServerInterceptor(zapLogger),
    16. grpc_auth.UnaryServerInterceptor(myAuthFunction),
    17. grpc_recovery.UnaryServerInterceptor(),
    18. )),
    19. )
    grpc.StreamInterceptor中添加流式RPC的拦截器。
    grpc.UnaryInterceptor中添加简单RPC的拦截器。

    grpc_zap日志记录

  1. 创建zap.Logger实例

    1. func ZapInterceptor() *zap.Logger {
    2. logger, err := zap.NewDevelopment()
    3. if err != nil {
    4. log.Fatalf("failed to initialize zap logger: %v", err)
    5. }
    6. grpc_zap.ReplaceGrpcLogger(logger)
    7. return logger
    8. }
  2. 把zap拦截器添加到服务端

    1. grpcServer := grpc.NewServer(
    2. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
    3. grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
    4. )),
    5. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
    6. grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
    7. )),
    8. )
  3. 日志分析

每日一库之99:go-grpc-middleware - 图1
各个字段代表的意思如下:

  1. {
  2. "level": "info", // string zap log levels
  3. "msg": "finished unary call", // string log message
  4. "grpc.code": "OK", // string grpc status code
  5. "grpc.method": "Ping", // string method name
  6. "grpc.service": "mwitkow.testproto.TestService", // string full name of the called service
  7. "grpc.start_time": "2006-01-02T15:04:05Z07:00", // string RFC3339 representation of the start time
  8. "grpc.request.deadline": "2006-01-02T15:04:05Z07:00", // string RFC3339 deadline of the current request if supplied
  9. "grpc.request.value": "something", // string value on the request
  10. "grpc.time_ms": 1.345, // float32 run time of the call in ms
  11. "peer.address": {
  12. "IP": "127.0.0.1", // string IP address of calling party
  13. "Port": 60216, // int port call is coming in on
  14. "Zone": "" // string peer zone for caller
  15. },
  16. "span.kind": "server", // string client | server
  17. "system": "grpc", // string
  18. "custom_field": "custom_value", // string user defined field
  19. "custom_tags.int": 1337, // int user defined tag on the ctx
  20. "custom_tags.string": "something" // string user defined tag on the ctx
  21. }
  1. 把日志写到文件中

上面日志是在控制台输出的,现在我们把日志写到文件中,修改ZapInterceptor方法。

  1. import (
  2. grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
  3. "go.uber.org/zap"
  4. "go.uber.org/zap/zapcore"
  5. "gopkg.in/natefinch/lumberjack.v2"
  6. )
  7. // ZapInterceptor 返回zap.logger实例(把日志写到文件中)
  8. func ZapInterceptor() *zap.Logger {
  9. w := zapcore.AddSync(&lumberjack.Logger{
  10. Filename: "log/debug.log",
  11. MaxSize: 1024, //MB
  12. LocalTime: true,
  13. })
  14. config := zap.NewProductionEncoderConfig()
  15. config.EncodeTime = zapcore.ISO8601TimeEncoder
  16. core := zapcore.NewCore(
  17. zapcore.NewJSONEncoder(config),
  18. w,
  19. zap.NewAtomicLevel(),
  20. )
  21. logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
  22. grpc_zap.ReplaceGrpcLogger(logger)
  23. return logger
  24. }

grpc_auth认证

go-grpc-middleware中的grpc_auth默认使用authorization认证方式,以authorization为头部,包括basic, bearer形式等。下面介绍bearer token认证。bearer允许使用access key(如JSON Web Token (JWT))进行访问。

  1. 新建grpc_auth服务端拦截器 ```go // TokenInfo 用户信息 type TokenInfo struct { ID string Roles []string }

// AuthInterceptor 认证拦截器,对以authorization为头部,形式为bearer token的Token进行验证 func AuthInterceptor(ctx context.Context) (context.Context, error) { token, err := grpc_auth.AuthFromMD(ctx, “bearer”) if err != nil { return nil, err } tokenInfo, err := parseToken(token) if err != nil { return nil, grpc.Errorf(codes.Unauthenticated, “ %v”, err) } //使用context.WithValue添加了值后,可以用Value(key)方法获取值 newCtx := context.WithValue(ctx, tokenInfo.ID, tokenInfo) //log.Println(newCtx.Value(tokenInfo.ID)) return newCtx, nil }

//解析token,并进行验证 func parseToken(token string) (TokenInfo, error) { var tokenInfo TokenInfo if token == “grpc.auth.token” { tokenInfo.ID = “1” tokenInfo.Roles = []string{“admin”} return tokenInfo, nil } return tokenInfo, errors.New(“Token无效: bearer “ + token) }

//从token中获取用户唯一标识 func userClaimFromToken(tokenInfo TokenInfo) string { return tokenInfo.ID }

  1. 代码中的对token进行简单验证并返回模拟数据。
  2. 2. **客户端请求添加bearer token**
  3. 实现和上篇的自定义认证方法大同小异。gRPC 中默认定义了 PerRPCCredentials,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:
  4. - GetRequestMetadata:获取当前请求认证所需的元数据
  5. - RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输
  6. 接下来我们实现这两个方法
  7. ```go
  8. // Token token认证
  9. type Token struct {
  10. Value string
  11. }
  12. const headerAuthorize string = "authorization"
  13. // GetRequestMetadata 获取当前请求认证所需的元数据
  14. func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  15. return map[string]string{headerAuthorize: t.Value}, nil
  16. }
  17. // RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
  18. func (t *Token) RequireTransportSecurity() bool {
  19. return true
  20. }

注意:这里要以authorization为头部,和服务端对应。

发送请求时添加token

  1. //从输入的证书文件中为客户端构造TLS凭证
  2. creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")
  3. if err != nil {
  4. log.Fatalf("Failed to create TLS credentials %v", err)
  5. }
  6. //构建Token
  7. token := auth.Token{
  8. Value: "bearer grpc.auth.token",
  9. }
  10. // 连接服务器
  11. conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))

注意:Token中的Value的形式要以bearer token值形式。因为我们服务端使用了bearer token验证方式。

  1. 把grpc_auth拦截器添加到服务端

    1. grpcServer := grpc.NewServer(cred.TLSInterceptor(),
    2. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
    3. grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
    4. grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
    5. )),
    6. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
    7. grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
    8. grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
    9. )),
    10. )

    写到这里,服务端都会拦截请求并进行bearer token验证,使用bearer token是规范了与HTTP请求的对接,毕竟gRPC也可以同时支持HTTP请求。

    grpc_recovery恢复

    把gRPC中的panic转成error,从而恢复程序。

  2. 直接把grpc_recovery拦截器添加到服务端

最简单使用方式

  1. grpcServer := grpc.NewServer(cred.TLSInterceptor(),
  2. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
  3. grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
  4. grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
  5. grpc_recovery.StreamServerInterceptor,
  6. )),
  7. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
  8. grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
  9. grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
  10. grpc_recovery.UnaryServerInterceptor(),
  11. )),
  12. )

2.自定义错误返回
当panic时候,自定义错误码并返回。

  1. // RecoveryInterceptor panic时返回Unknown错误吗
  2. func RecoveryInterceptor() grpc_recovery.Option {
  3. return grpc_recovery.WithRecoveryHandler(func(p interface{}) (err error) {
  4. return grpc.Errorf(codes.Unknown, "panic triggered: %v", p)
  5. })
  6. }

添加grpc_recovery拦截器到服务端

  1. grpcServer := grpc.NewServer(cred.TLSInterceptor(),
  2. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
  3. grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
  4. grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
  5. grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()),
  6. )),
  7. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
  8. grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
  9. grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
  10. grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()),
  11. )),
  12. )

总结

本篇介绍了go-grpc-middleware中的grpc_zap、grpc_auth和grpc_recovery拦截器的使用。go-grpc-middleware中其他拦截器可参考GitHub学习使用。
教程源码地址:https://github.com/go-quiz/go-grpc-example