ASP.NET Core gRPC でフィルターみたいなことをしたいんだけど

2 min read読了の目安(約1900字

ASP.NET Core gRPC で全部のサービスの呼び出し前とかに共通処理を入れたい!!そんなとき ASP.NET Core MVC とかで使えるフィルターのようなものがないかな?と思って探してみると Interceptor というものがありました。クライアントサイドやサーバーサイドの Interceptor があるみたいですが、今回はサーバーサイドの方をやってみてます。

因みに、ちょっと古い記事ですけど golang だとこんな風に使えるみたいです。

https://soichisumi.net/2019/12/grpc-interceptor-method-setting/

ASP.NET Core gRPC の場合は、Startup.cs の ConfigureServicesservices.AddGrpc の呼び出しのところにオプションんを構成するオーバーライドがあるので、そこで Interceptor を設定できます。

Interceptor は名前のとおり Grpc.Core.Interceptors.Interceptor を継承して作って、前処理や後処理を入れたいものに対応したメソッドをオーバーライドして作ります。
純粋なクライアントからサーバーのメソッドを読んで結果を返すタイプの場合は UnaryServerHandler メソッドをオーバーライドします。例えば呼び出し前にログを出す場合は以下のような Interceptor を作ります。

LoggingInterceptor.cs
class LoggingInterceptor : Interceptor
{
    private readonly ILogger<LoggingInterceptor> logger;

    public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
    {
        this.logger = logger;
    }

    public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
    {
        this.logger.LogInformation($"★ {context.Host}, {context.Method}");
        return base.UnaryServerHandler(request, context, continuation);
    }
}

ServerCallContext の Method に /greet.Greeter/SayHello のような値が入っているのでメソッドごとに固有の処理をしたい場合は、ここを見て処理をすることになると思います。
この文字列から対象のクラスを特定して、属性をなめて色々やることも出来そうですね。(そんな仕組み元々あったりするのだろうか?)

request 引数には実際にクライアントから呼び出し時に渡されたオブジェクトが入っているのですが、このシグネチャーだと object 型を扱ってるのと変わらないので Method を見て適時キャストしてやることになるのかなぁと思います。

この Interceptor を登録するには ConfigureServices メソッドで以下のように AddGrpcoptions にある InterceptorsAdd することで登録できます。

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<LoggingInterceptor>();
    });
}

この Interceptor を追加した状態でサービスを呼び出すと、以下のようなログが出ます。

info: GrpcService1.LoggingInterceptor[0]
      ★ localhost:5001, /greet.Greeter/SayHello