原文地址:帶入gRPC:Unary and Stream interceptor
項目地址:https://github.com/EDDYCJY/go...html
我想在每一個 RPC 方法的前或後作某些事情,怎麼作?git
本章節將要介紹的攔截器(interceptor),就能幫你在合適的地方實現這些功能 🤫github
在 gRPC 中,大類可分爲兩種 RPC 方法,與攔截器的對應關係是:golang
func UnaryInterceptor(i UnaryServerInterceptor) ServerOption { return func(o *options) { if o.unaryInt != nil { panic("The unary server interceptor was already set and may not be reset.") } o.unaryInt = i } }
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
經過查看源碼可得知,要完成一個攔截器須要實現 UnaryServerInterceptor
方法。形參以下:segmentfault
func StreamInterceptor(i StreamServerInterceptor) ServerOption
type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error
StreamServerInterceptor 與 UnaryServerInterceptor 形參的意義是同樣,再也不贅述tcp
另外,能夠發現 gRPC 自己竟然只能設置一個攔截器,難道全部的邏輯都只能寫在一塊兒?google
關於這一點,你能夠放心。採用開源項目 go-grpc-middleware 就能夠解決這個問題,本章也會使用它 😄debug
import "github.com/grpc-ecosystem/go-grpc-middleware" myServer := grpc.NewServer( grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( ... )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( ... )), )
從本節開始編寫 gRPC interceptor 的代碼,咱們會將實現如下攔截器:3d
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 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) }
import ( "context" "crypto/tls" "crypto/x509" "errors" "io/ioutil" "log" "net" "runtime/debug" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/status" "google.golang.org/grpc/codes" "github.com/grpc-ecosystem/go-grpc-middleware" pb "github.com/EDDYCJY/go-grpc-example/proto" ) ... func main() { c, err := GetTLSCredentialsByCA() if err != nil { log.Fatalf("GetTLSCredentialsByCA err: %v", err) } opts := []grpc.ServerOption{ grpc.Creds(c), grpc_middleware.WithUnaryServerChain( RecoveryInterceptor, LoggingInterceptor, ), } server := grpc.NewServer(opts...) pb.RegisterSearchServiceServer(server, &SearchService{}) lis, err := net.Listen("tcp", ":"+PORT) if err != nil { log.Fatalf("net.Listen err: %v", err) } server.Serve(lis) }
啓動 simple_server/server.go,執行 simple_client/client.go 發起請求,獲得結果:日誌
$ go run server.go 2018/10/02 13:46:35 gRPC method: /proto.SearchService/Search, request:"gRPC" 2018/10/02 13:46:35 gRPC method: /proto.SearchService/Search, response:"gRPC Server"
在 RPC 方法中人爲地製造運行時錯誤,再重複啓動 server/client.go,獲得結果:
$ go run client.go 2018/10/02 13:19:03 client.Search err: rpc error: code = Internal desc = Panic err: assignment to entry in nil map exit status 1
$ go run server.go goroutine 23 [running]: runtime/debug.Stack(0xc420223588, 0x1033da9, 0xc420001980) /usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:24 +0xa7 runtime/debug.PrintStack() /usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:16 +0x22 main.RecoveryInterceptor.func1(0xc420223a10) ...
檢查服務是否仍然運行,便可知道 Recovery 是否成功生效
經過本章節,你能夠學會最多見的攔截器使用方法。接下來其它「新」需求只要觸類旁通便可 😈