Go?gRPC服務(wù)進(jìn)階middleware使用教程
前言
之前介紹了gRPC中TLS認(rèn)證和自定義方法認(rèn)證,最后還簡(jiǎn)單介紹了gRPC攔截器的使用。gRPC自身只能設(shè)置一個(gè)攔截器,所有邏輯都寫(xiě)一起會(huì)比較亂。本篇簡(jiǎn)單介紹go-grpc-middleware的使用,包括grpc_zap、grpc_auth和grpc_recovery。
go-grpc-middleware簡(jiǎn)介
go-grpc-middleware封裝了認(rèn)證(auth), 日志( logging), 消息(message), 驗(yàn)證(validation), 重試(retries) 和監(jiān)控(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中添加簡(jiǎn)單RPC的攔截器。
grpc_zap日志記錄
1.創(chuàng)建zap.Logger實(shí)例
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攔截器添加到服務(wù)端
grpcServer := grpc.NewServer( grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()), )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()), )), )
3.日志分析

各個(gè)字段代表的意思如下:
{
"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.把日志寫(xiě)到文件中
上面日志是在控制臺(tái)輸出的,現(xiàn)在我們把日志寫(xiě)到文件中,修改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實(shí)例(把日志寫(xiě)到文件中)
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認(rèn)證
go-grpc-middleware中的grpc_auth默認(rèn)使用authorization認(rèn)證方式,以authorization為頭部,包括basic, bearer形式等。下面介紹bearer token認(rèn)證。bearer允許使用access key(如JSON Web Token (JWT))進(jìn)行訪問(wèn)。
1.新建grpc_auth服務(wù)端攔截器
// TokenInfo 用戶(hù)信息
type TokenInfo struct {
ID string
Roles []string
}
// AuthInterceptor 認(rèn)證攔截器,對(duì)以authorization為頭部,形式為`bearer token`的Token進(jìn)行驗(yàn)證
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,并進(jìn)行驗(yàn)證
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無(wú)效: bearer " + token)
}
//從token中獲取用戶(hù)唯一標(biāo)識(shí)
func userClaimFromToken(tokenInfo TokenInfo) string {
return tokenInfo.ID
}代碼中的對(duì)token進(jìn)行簡(jiǎn)單驗(yàn)證并返回模擬數(shù)據(jù)。
2.客戶(hù)端請(qǐng)求添加bearer token
實(shí)現(xiàn)和上篇的自定義認(rèn)證方法大同小異。gRPC 中默認(rèn)定義了 PerRPCCredentials,是提供用于自定義認(rèn)證的接口,它的作用是將所需的安全認(rèn)證信息添加到每個(gè)RPC方法的上下文中。其包含 2 個(gè)方法:
GetRequestMetadata:獲取當(dāng)前請(qǐng)求認(rèn)證所需的元數(shù)據(jù)
RequireTransportSecurity:是否需要基于 TLS 認(rèn)證進(jìn)行安全傳輸
接下來(lái)我們實(shí)現(xiàn)這兩個(gè)方法
// Token token認(rèn)證
type Token struct {
Value string
}
const headerAuthorize string = "authorization"
// GetRequestMetadata 獲取當(dāng)前請(qǐng)求認(rèn)證所需的元數(shù)據(jù)
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{headerAuthorize: t.Value}, nil
}
// RequireTransportSecurity 是否需要基于 TLS 認(rèn)證進(jìn)行安全傳輸
func (t *Token) RequireTransportSecurity() bool {
return true
}注意:這里要以authorization為頭部,和服務(wù)端對(duì)應(yīng)。
發(fā)送請(qǐng)求時(shí)添加token
//從輸入的證書(shū)文件中為客戶(hù)端構(gòu)造TLS憑證
creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")
if err != nil {
log.Fatalf("Failed to create TLS credentials %v", err)
}
//構(gòu)建Token
token := auth.Token{
Value: "bearer grpc.auth.token",
}
// 連接服務(wù)器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))注意:Token中的Value的形式要以bearer token值形式。因?yàn)槲覀兎?wù)端使用了bearer token驗(yàn)證方式。
3.把grpc_auth攔截器添加到服務(wù)端
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()), )), )
寫(xiě)到這里,服務(wù)端都會(huì)攔截請(qǐng)求并進(jìn)行bearer token驗(yàn)證,使用bearer token是規(guī)范了與HTTP請(qǐng)求的對(duì)接,畢竟gRPC也可以同時(shí)支持HTTP請(qǐng)求。
grpc_recovery恢復(fù)
把gRPC中的panic轉(zhuǎn)成error,從而恢復(fù)程序。
1.直接把grpc_recovery攔截器添加到服務(wù)端
最簡(jiǎn)單使用方式
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.自定義錯(cuò)誤返回
當(dāng)panic時(shí)候,自定義錯(cuò)誤碼并返回。
// RecoveryInterceptor panic時(shí)返回Unknown錯(cuò)誤嗎
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攔截器到服務(wù)端
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()),
)),
)總結(jié)
本篇介紹了go-grpc-middleware中的grpc_zap、grpc_auth和grpc_recovery攔截器的使用。go-grpc-middleware中其他攔截器可參考GitHub學(xué)習(xí)使用。
教程源碼地址:https://github.com/Bingjian-Zhu/go-grpc-example
以上就是Go gRPC服務(wù)進(jìn)階middleware使用教程的詳細(xì)內(nèi)容,更多關(guān)于Go gRPC服務(wù)middleware的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- golang通用的grpc?http基礎(chǔ)開(kāi)發(fā)框架使用快速入門(mén)
- Go創(chuàng)建Grpc鏈接池實(shí)現(xiàn)過(guò)程詳解
- go?doudou開(kāi)發(fā)gRPC服務(wù)快速上手實(shí)現(xiàn)詳解
- Golang?gRPC?HTTP協(xié)議轉(zhuǎn)換示例
- Go Grpc Gateway兼容HTTP協(xié)議文檔自動(dòng)生成網(wǎng)關(guān)
- Go?gRPC進(jìn)階教程gRPC轉(zhuǎn)換HTTP
- Go?gRPC服務(wù)proto數(shù)據(jù)驗(yàn)證進(jìn)階教程
- Go?gRPC進(jìn)階教程服務(wù)超時(shí)設(shè)置
- Go語(yǔ)言與gRPC的完美結(jié)合實(shí)戰(zhàn)演練
相關(guān)文章
看看你的Go應(yīng)用是否用了正確CPU核數(shù)
這篇文章主要為大家介紹了Go應(yīng)用正確的CPU核數(shù)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Linux環(huán)境下編譯并運(yùn)行g(shù)o項(xiàng)目的全過(guò)程
Go語(yǔ)言是Google的開(kāi)源編程語(yǔ)言,廣泛應(yīng)用于云計(jì)算、分布式系統(tǒng)開(kāi)發(fā)等領(lǐng)域,在Linux上也有大量的應(yīng)用場(chǎng)景,這篇文章主要給大家介紹了關(guān)于Linux環(huán)境下編譯并運(yùn)行g(shù)o項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2023-11-11
在?Golang?中使用?Cobra?創(chuàng)建?CLI?應(yīng)用
這篇文章主要介紹了在?Golang?中使用?Cobra?創(chuàng)建?CLI?應(yīng)用,來(lái)看下?Cobra?的使用,這里我們使用的?go1.13.3?版本,使用?Go?Modules?來(lái)進(jìn)行包管理,需要的朋友可以參考下2022-01-01
golang利用不到20行代碼實(shí)現(xiàn)路由調(diào)度詳解
這篇文章主要給大家介紹了關(guān)于golang利用不到20行代碼實(shí)現(xiàn)路由調(diào)度的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
Go語(yǔ)言中?Print?Printf和Println?的區(qū)別解析
這篇文章主要介紹了Go語(yǔ)言中?Print?Printf和Println?的區(qū)別,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
Golang 使用接口實(shí)現(xiàn)泛型的方法示例
這篇文章主要介紹了Golang 使用接口實(shí)現(xiàn)泛型的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
基于context.Context的Golang?loader緩存請(qǐng)求放大問(wèn)題解決
這篇文章主要為大家介紹了基于context.Context的Golang?loader緩存請(qǐng)求放大解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

