きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

Go の gRPC のシンプルな Interceptor を自作して理解を深める

Go の gPRC のシンプルな Interceptor を適当に書いたら割とすんなり動いたので基本的な部分をまとめていきます。

今回のサンプルは以下のリポジトリに置いてあります。

go-grpc-middleware にロガーなど様々なミドルウェア(= Interceptor)が用意されていますし、本番用途であればそれらメンテナンスや検証の行き届いたパッケージを使うべきだと思いますが、初見のパッケージのオプションを眺めるのも面倒なのでロガー程度ならスクラッチできないかなと思ってやってみたら意外と簡単でした。基本的に入出力さえ辻褄を合わせておけば何をやるのも自由そうです。

動機としてはもう一つ、grpcフレームワーク的なインタフェースに興味があったというのもありました。既製品のミドルウェアを使う前に基本的なインタフェースだけでも押さえておくと、開発の自由度も上がるかもしれないという目論見です。

それでは以下、今回行ったことの簡単な解説です。

Interceptor を自作して grpc.Server に組み込んで実行する

Interceptor を定義する

今回はミドルウェアとしては定番のリクエストのログを出力する Interceptor を題材にしました。Interceptor について調べる前はやっつけでメソッドごとの各ハンドラに log.Printf を突っ込んで回る作業をしていましたが、それらのコードも一掃できました。

Interceptor は func として定義します。Interceptor には種類があり、今回実装したのは grpc.UnaryServerInterceptor という stream でない値を返す際に動作する Interceptor です。

grpc.UnaryServerInterceptorシグネチャに従い、メソッド名・リクエスト・レスポンスを log として表示する Interceptor は以下のようになりました。引数のシグネチャが長いですがミドルウェア的な処理としては必要な情報が並んでいるだけですね。

func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {

    // ハンドラを実行する
    res, err := handler(ctx, req)

    // リクエスト処理のログを表示する(今回ミドルウェアでやりたかったこと ^-^)
    log.Println("My interceptor called!")
    log.Printf("%s: %v -> %v", info.FullMethod, req, res)

    // 実行結果とエラーを返す(返さないとサーバが結果を返さなくなる ^-^);
    return res, err
}

特に難しいことはやっておらず、関数評価時に渡される reqinfo などの材料をもとに処理を行っているだけです。

一点気になったのが Interceptor 中でメソッドのハンドラを評価し、結果を返さなければならないという点です。そこまでミドルウェアというものを経験したこともないのですが、こういう形式が普通なんでしょうか。Node.js の Express とはお作法が微妙に違うなという感触を得ました。

ちなみに resnil で置き換えたらサーバが空レスポンスを返すようになりました。

grpc.Server に設定する

Interceptor を ServerOption type に変換する grpc.UnaryInterceptor を通して Interceptor をサーバに設定します。

grpcServer := grpc.NewServer(
  grpc.UnaryInterceptor(loggingInterceptor),
)

ここもそこまで難解な見た目にはなっていませんね。シンプルです。

実行

サーバを実行してリクエストを送ったところ、サーバのログが以下のように表示されました。

2018/09/13 22:20:39 gRPC server starts on localhost:10000
2018/09/13 22:20:41 My interceptor called!
2018/09/13 22:20:41 /hello.Greeting/Hello: name:"asa-taka"  -> message:"Hello asa-taka, I am gentle-server"

...というこんな感じで grpc の Interceptor は自作するのも結構簡単なんだな、というお話でした。

余談

開発って

  • その分野のベストプラクティス・定番のライブラリを調べる
  • 調べるのが面倒だからとりあえず手前味噌で実装する

のバランスを探り探りで行なっていく作業だと思うのですが、技術検証フェーズでは後者を自由度高く行えると快適に思えることが多いです。ミドルウェアのインタフェースを知ることも、大いにそれに与するものだと思っております。