概要
以前
にてgRPCのUnary Interceptorの基本的な使い方を説明しました。
このInterceptorを複数設定したい時に使われるのがgo-grpc-middlewareの出している
です。READMEにありますが、以下のように複数のInteceptorをセットできます。
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(), )), )
ここで実行順序について疑問に思ったので確認してみました。
環境
- go 1.12.7
- go-grpc-middleware master
実行順序
それではUnaryServerInterceptor
の挙動を確認してみます。
Interceptorが1つの場合
前の記事でも紹介したように1つの場合は以下です。
func LogBodyInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { // 1. request bodyをprint fmt.Printf("%+v\n", req) // 2. リクエストされたgrpcメソッドを実行 resp, err := handler(ctx, req) if err != nil { return nil, err } // 3. response bodyをprint fmt.Printf("%+v\n", resp) return res, nil } }
1, 2, 3と順に実行されます。
重要なのはhandlerの後に書けばgrpcメソッドを実行した後に処理される、という点です。
複数にした場合
では複数にした場合どうなるでしょうか?
Interceptor
func ServerInterceptor(prefix string) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (interface{}, error) { fmt.Println(prefix, "before handler") // call handler resp, err := handler(ctx, req) fmt.Println(prefix, "after handler") return resp, err } }
このような形のInterceptorを用意します。
セット
サーバ側では以下のようにセットします。
分かりやすいようにセットする順にserver-interN-
というprefixを渡してます。
s := grpc.NewServer( grpc.UnaryInterceptor(interceptor.ServerInterceptor("server-inter1-")), grpc.UnaryInterceptor(interceptor.ServerInterceptor("server-inter2-")), grpc.UnaryInterceptor(interceptor.ServerInterceptor("server-inter3-")), )
結果
結果は以下です。
server-inter1- before handler server-inter2- before handler server-inter3- before handler handler server-inter3- after handler server-inter2- after handler server-inter1- after handler
ポイント
- handlerの前に書いたらChainした順に実行される
- handlerの後に書いたらdeferのように最後から順に実行される
handler前の処理は想定通りだと思いますが、handler後の処理はdeferのように逆順になるところが注意です。
handlerの後に実行する処理としてよくあるのは?
- アクセスlogを出力
- errorハンドリングの統合(通常のエラーをgRPCエラーへ変換)
- panicのハンドリング
などがあります。
今回の順序を把握していないとハマるのでご注意ください。
Client Interceptorは?
UnaryClientInterceptor
が複数のケースも確認してみます。
Interceptor
以下のようにinvoker()
の前後にログを入れます。
func ClientInterceptor(prefix string) grpc.UnaryClientInterceptor { return func( ctx context.Context, method string, req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, ) error { fmt.Println(prefix, "before invoker") err := invoker(ctx, method, req, reply, cc, opts...) fmt.Println(prefix, "after invoker") return err } }
Client実行側
分かりやすいようにセットする順にclient-interN-
というprefixを渡してます。
func run() { conn, err := grpc.Dial( address, grpc.WithInsecure(), grpc.WithUnaryInterceptor(interceptor.ClientInterceptor("client-inter1-")), grpc.WithUnaryInterceptor(interceptor.ClientInterceptor("client-inter2-")), grpc.WithUnaryInterceptor(interceptor.ClientInterceptor("client-inter3-")), )
結果
client-inter1- before invoker client-inter2- before invoker client-inter3- before invoker client-inter3- after invoker client-inter2- after invoker client-inter1- after invoker 2019/07/11 19:44:59 Reply: Hello alice
ポイント
- invokerの前に書いたらChainした順に実行される
- invokerの後に書いたらdeferのように最後から順に実行される
こちらもServerの時同様の挙動ですね。
サンプルコード
今回検証したコードはこちらです
まとめ
Server/ClientのInterceptorを複数設定した時の挙動について検証しました。