Go gRPC服務進階middleware使用教程
前言
之前介紹瞭gRPC中TLS認證和自定義方法認證,最後還簡單介紹瞭gRPC攔截器的使用。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
使用
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實例
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攔截器添加到服務端
grpcServer := grpc.NewServer( grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()), )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()), )), )
3.日志分析
各個字段代表的意思如下:
{ "level": "info", // string zap log levels "msg": "finished unary call", // string log message "grpc.code": "OK", // string grpc status code "grpc.method": "Ping", / string method name "grpc.service": "mwitkow.testproto.TestService", // string full name of the called service "grpc.start_time": "2006-01-02T15:04:05Z07:00", // string RFC3339 representation of the start time "grpc.request.deadline": "2006-01-02T15:04:05Z07:00", // string RFC3339 deadline of the current request if supplied "grpc.request.value": "something", // string value on the request "grpc.time_ms": 1.345, // float32 run time of the call in ms "peer.address": { "IP": "127.0.0.1", // string IP address of calling party "Port": 60216, // int port call is coming in on "Zone": "" // string peer zone for caller }, "span.kind": "server", // string client | server "system": "grpc", // string "custom_field": "custom_value", // string user defined field "custom_tags.int": 1337, // int user defined tag on the ctx "custom_tags.string": "something" // string user defined tag on the ctx }
4.把日志寫到文件中
上面日志是在控制臺輸出的,現在我們把日志寫到文件中,修改ZapInterceptor方法。
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" ) // ZapInterceptor 返回zap.logger實例(把日志寫到文件中) func ZapInterceptor() *zap.Logger { w := zapcore.AddSync(&lumberjack.Logger{ Filename: "log/debug.log", MaxSize: 1024, //MB 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服務端攔截器
// 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 }
代碼中的對token進行簡單驗證並返回模擬數據。
2.客戶端請求添加bearer token
實現和上篇的自定義認證方法大同小異。gRPC 中默認定義瞭 PerRPCCredentials,是提供用於自定義認證的接口,它的作用是將所需的安全認證信息添加到每個RPC方法的上下文中。其包含 2 個方法:
GetRequestMetadata:獲取當前請求認證所需的元數據
RequireTransportSecurity:是否需要基於 TLS 認證進行安全傳輸
接下來我們實現這兩個方法
// Token token認證 type Token struct { Value string } const headerAuthorize string = "authorization" // GetRequestMetadata 獲取當前請求認證所需的元數據 func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{headerAuthorize: t.Value}, nil } // RequireTransportSecurity 是否需要基於 TLS 認證進行安全傳輸 func (t *Token) RequireTransportSecurity() bool { return true }
註意:這裡要以authorization為頭部,和服務端對應。
發送請求時添加token
//從輸入的證書文件中為客戶端構造TLS憑證 creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example") if err != nil { log.Fatalf("Failed to create TLS credentials %v", err) } //構建Token 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攔截器添加到服務端
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攔截器添加到服務端
最簡單使用方式
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時候,自定義錯誤碼並返回。
// RecoveryInterceptor panic時返回Unknown錯誤嗎 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攔截器到服務端
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_zap、grpc_auth和grpc_recovery攔截器的使用。go-grpc-middleware中其他攔截器可參考GitHub學習使用。
教程源碼地址:https://github.com/Bingjian-Zhu/go-grpc-example
以上就是Go gRPC服務進階middleware使用教程的詳細內容,更多關於Go gRPC服務middleware的資料請關註WalkonNet其它相關文章!