0%

前言#

上篇介绍了gRPC中TLS认证和自定义方法认证,最后还简单介绍了gRPC拦截器的使用。gRPC自身只能设置一个拦截器,所有逻辑都写一起会比较乱。本篇简单介绍go-grpc-middleware的使用,包括grpc_zapgrpc_authgrpc_recovery

go-grpc-middleware简介#

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

  • 安装 go get github.com/grpc-ecosystem/go-grpc-middleware
  • 使用

Copy

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(), )), )

grpc.StreamInterceptor中添加流式RPC的拦截器。
grpc.UnaryInterceptor中添加简单RPC的拦截器。

grpc_zap日志记录#

1.创建zap.Logger实例

Copy

func ZapInterceptor() *zap.Logger { logger, err := zap.NewDevelopment() if err != nil { log.Fatalf("failed to initialize zap logger: %v", err) } grpc_zap.ReplaceGrpcLogger(logger) return logger }

2.把zap拦截器添加到服务端

Copy

grpcServer := grpc.NewServer( grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()), )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()), )), )

3.日志分析


各个字段代表的意思如下:

Copy

`{
“level”: “info”,
“msg”: “finished unary call”,

  "grpc.code": "OK",						
  "grpc.method": "Ping",					/ string  method name
  "grpc.service": "mwitkow.testproto.TestService",              
  "grpc.start_time": "2006-01-02T15:04:05Z07:00",               
  "grpc.request.deadline": "2006-01-02T15:04:05Z07:00",         
  "grpc.request.value": "something",				
  "grpc.time_ms": 1.345,					

  "peer.address": {
    "IP": "127.0.0.1",						
    "Port": 60216,						
    "Zone": ""							
  },
  "span.kind": "server",					
  "system": "grpc",						

  "custom_field": "custom_value",				
  "custom_tags.int": 1337,					
  "custom_tags.string": "something"				

}`

4.把日志写到文件中

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

Copy

`import (
grpc_zap “github.com/grpc-ecosystem/go-grpc-middleware/logging/zap”
“go.uber.org/zap”
“go.uber.org/zap/zapcore”
“gopkg.in/natefinch/lumberjack.v2”
)

func ZapInterceptor() *zap.Logger {
w := zapcore.AddSync(&lumberjack.Logger{
Filename: “log/debug.log”,
MaxSize: 1024,
LocalTime: true,
})

config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.ISO8601TimeEncoder
core := zapcore.NewCore(
    zapcore.NewJSONEncoder(config),
    w,
    zap.NewAtomicLevel(),
)

logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
grpc_zap.ReplaceGrpcLogger(logger)
return logger

}`

grpc_auth认证#

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

1.新建grpc_auth服务端拦截器

Copy

`type TokenInfo struct {
ID string
Roles []string
}

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)
}

newCtx := context.WithValue(ctx, tokenInfo.ID, tokenInfo)

return newCtx, nil

}

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)
}

func userClaimFromToken(tokenInfo TokenInfo) string {
return tokenInfo.ID
}`

代码中的对token进行简单验证并返回模拟数据。

2.客户端请求添加bearer token

实现和上篇的自定义认证方法大同小异。gRPC 中默认定义了 PerRPCCredentials,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:

  • GetRequestMetadata:获取当前请求认证所需的元数据
  • RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输

接下来我们实现这两个方法

Copy

`type Token struct {
Value string
}

const headerAuthorize string = “authorization”

func (t *Token) GetRequestMetadata(ctx context.Context, uri …string) (map[string]string, error) {
return map[string]string{headerAuthorize: t.Value}, nil
}

func (t *Token) RequireTransportSecurity() bool {
return true
}`

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

发送请求时添加token

Copy

`creds, err := credentials.NewClientTLSFromFile(“../tls/server.pem”, “go-grpc-example”)
if err != nil {
log.Fatalf(“Failed to create TLS credentials %v”, err)
}

token := auth.Token{
    Value: "bearer grpc.auth.token",
}

conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))` 

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

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

Copy

grpcServer := grpc.NewServer(cred.TLSInterceptor(), grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc_auth.StreamServerInterceptor(auth.AuthInterceptor), grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()), )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor), grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()), )), )

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

grpc_recovery恢复#

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

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

最简单使用方式

Copy

grpcServer := grpc.NewServer(cred.TLSInterceptor(), grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc_auth.StreamServerInterceptor(auth.AuthInterceptor), grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()), grpc_recovery.StreamServerInterceptor, )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor), grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()), grpc_recovery.UnaryServerInterceptor(), )), )

2.自定义错误返回

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

Copy

func RecoveryInterceptor() grpc_recovery.Option { return grpc_recovery.WithRecoveryHandler(func(p interface{}) (err error) { return grpc.Errorf(codes.Unknown, "panic triggered: %v", p) }) }

添加grpc_recovery拦截器到服务端

Copy

grpcServer := grpc.NewServer(cred.TLSInterceptor(), grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc_auth.StreamServerInterceptor(auth.AuthInterceptor), grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()), grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()), )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor), grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()), grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()), )), )

总结#

本篇介绍了go-grpc-middleware中的grpc_zapgrpc_authgrpc_recovery拦截器的使用。go-grpc-middleware中其他拦截器可参考GitHub学习使用。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example