gRPC触ってみた(2)

プログラミング

※前回記事:

今回は、grpcのサーバ実装で様々なオプションを付与することができるServerOptionについて触れていきます。

ServerOption

grpcのサーバ側で使用するgrpc.Server構造体は、以下の関数を利用して初期化されます。

func NewServer(opt ...ServerOption) *Server {
    ....
}

引数に任意の数のServerOptionインターフェースを満たす構造体を渡すことで、credentialsやcodec、keepaliveを始めとして、様々な挙動をサーバー処理の前後に入れ込むことができます。

UnaryInterceptor / ChainUnaryInterceptor

例えば下記のUnaryInterceptor関数ではサーバ処理にフックを付与するUnaryServerInterceptorを引数に渡すことで、ServerOptionを返します。

これをNewServerに渡すことでUnaryServerInterceptorで定義した処理をリクエスト受け取り時に実行できるようになります。

grpc package - google.golang.org/grpc - Go Packages
Package grpc implements an RPC system called gRPC.

こちらがUnaryServerInterceptorの定義です。

引数のhandler(UnaryHandler)はサービスメソッドのラッパーであり、UnaryServerInterceptor内で必ず実行する必要があります。

grpc package - google.golang.org/grpc - Go Packages
Package grpc implements an RPC system called gRPC.

UnaryInterceptorの実装例です。

func main() {
    ....
    grpcServer := grpc.NewServer(
        grpc.UnaryInterceptor(
            logging(),
        ),
    )
    ....
}

func logging() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        log.Printf("サーバ処理前のログ")

        resp, err = handler(ctx, req) // handlerの実行は必須です。ここが本来のサーバ処理を実行しています。
        if err != nil {
            return nil, err
        }

        log.Printf("サーバ処理後のログ")

        return resp, nil
    }
}

logging()関数で返しているUnaryServerInterceptor内で、本来のサーバ処理に加えてlog.Printfでログ出し処理を追加しています。

UnaryInterceptor関数ではUnaryServerInterceptorを1つしか指定できませんが、ChainUnaryInterceptorを使用すれば複数のUnaryServerInterceptorを設定できます。

grpc package - google.golang.org/grpc - Go Packages
Package grpc implements an RPC system called gRPC.

以下、ChainUnaryInterceptorの実装例です。

func main() {
    ....
    grpcServer := grpc.NewServer(
        grpc.ChainUnaryInterceptor(
            logging1(),
            logging2(),
        ),
    )
    ....
}

func logging1() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        log.Printf("サーバ処理前のログ1")

        resp, err = handler(ctx, req)
        if err != nil {
            return nil, err
        }

        log.Printf("サーバ処理後のログ1")

        return resp, nil
    }
}

func logging2() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        log.Printf("サーバ処理前のログ2")

        resp, err = handler(ctx, req)
        if err != nil {
            return nil, err
        }

        log.Printf("サーバ処理後のログ2")

        return resp, nil
    }
}

ChainUnaryInterceptorで指定する複数のUnaryServerInterceptorは、最初に指定したものがラッパーの一番外側、最後が一番内側になります。

なので上記の例だと、処理の順番は

ログ出力「サーバ処理前のログ1」
↓
ログ出力「サーバ処理前のログ2」
↓
サーバ処理
↓
ログ出力「サーバ処理後のログ2」
↓
ログ出力「サーバ処理後のログ1」

となります。

MaxSendMsgSize

MaxSendMsgSize関数は、サーバが返すデータの最大バイト数を指定するServerOptionを返します。

grpc package - google.golang.org/grpc - Go Packages
Package grpc implements an RPC system called gRPC.

サーバが返そうとするデータサイズが指定バイト数よりも大きい場合、以下のようなエラーレスポンスを返します。

2022/11/03 14:48:13 rpc error: code = ResourceExhausted desc = grpc: trying to send message larger than max (109 vs. 1)

上記ではエラーコードがResourceExhaustedとなっていますが、grpcのステータスコードは以下のように、uint32のエイリアスであるCode型で表現されます。

codes package - google.golang.org/grpc/codes - Go Packages
Package codes defines the canonical error codes used by gRPC.

サーバ側で任意のステータスコードを返したい時はこの中の定数から指定してあげれば良いでしょう。

HTTPのステータスコードとは異なるので注意してください。


今回は2例のみ挙げましたが、ServerOptionを設定できる関数は他にも色々あります。

割と柔軟な設定ができるので今後もいろいろと触っていきたいなーと思ってます。

おまけ: go-grpc-middleware

GitHub - grpc-ecosystem/go-grpc-middleware: Golang gRPC Middlewares: interceptor chaining, auth, logging, retries and more.
Golang gRPC Middlewares: interceptor chaining, auth, logging, retries and more. - GitHub - grpc-ecosystem/go-grpc-middleware: Golang gRPC Middlewares: intercept...

こちらのパッケージを使用することで各種Interceptorの実装が容易になり、お手軽なマイクロサービス構築が可能となります。

以下の例では、複数のUnaryServerInterceptorを1つにまとめることができるChainUnaryServerを実装しています。

import (
    ....
    grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
)

func main() {
    ....
    grpcServer := grpc.NewServer(
        grpc.UnaryInterceptor(
            grpc_middleware.ChainUnaryServer(
                logging1(),
                logging2(),
            ),
        ),
    )
    ....
}

func logging1() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        log.Printf("サーバ処理前のログ1")

        resp, err = handler(ctx, req)
        if err != nil {
            return nil, err
        }

        log.Printf("サーバ処理後のログ1")

        return resp, nil
    }
}

func logging2() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        log.Printf("サーバ処理前のログ2")

        resp, err = handler(ctx, req)
        if err != nil {
            return nil, err
        }

        log.Printf("サーバ処理後のログ2")

        return resp, nil
    }
}
この記事を書いた人
rio

英語を使う職業プログラマー。
英会話や英語学習は大好きなのに資格を取るのが面倒すぎ、最近ようやく重い腰を上げてTOEICを受験。
自身の英語力を信じてぶっつけ本番で挑み、945点取得。
プログラミングは最近ではGo言語、Typescript、Javascriptをよく書いている。

このブログでは、英会話や英語学習のトピックや、プログラミング関連の記事を執筆していきます。

rioをフォローする
プログラミング
rioをフォローする
えいごギークのほっとtime

コメント

タイトルとURLをコピーしました