xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

gRPC / MagicOnion 入門 (9) - 明示的にステータスコードを返す

HTTP/1.1 にステータスコードというレスポンスの意味を表す数値があったように、HTTP/2 をベースとする gRPC にもステータスコードがあります。これまでの解説ではレスポンスとして正常値のみを返していたので、HTTP/1.1 のステータスコードで言うところの 200/OK にあたるものが返っていました。今回はエラーハンドリングなどをした際に利用する、独自のステータスコードを返す方法について見ていきます。

サーバー側でステータスコードを返す

サーバー側からステータスコードを返すのは非常に簡単で、ReturnStatusCode メソッドを利用するだけです。例えば以下のように、エラーが発生したときだけエラーステータスを返します。

using System;
using Grpc.Core;
using MagicOnion;
using MagicOnion.Server;
using MagicOnionSample.ServiceDefinition;
using MessagePack;

namespace MagicOnionSample.Service
{
    public class SampleApi : ServiceBase<ISampleApi>, ISampleApi
    {
        public async UnaryResult<Nil> Sample()
        {
            try
            {
                //--- 何かエラーが起こったことにする
                throw new Exception("エラーだよ ☆(ゝω・)vキャピ");
                return Nil.Default;  //--- 正常系
            }
            catch (Exception ex)
            {
                //--- 異常系ではステータスコード + エラー詳細を返す
                return this.ReturnStatusCode<Nil>((int)StatusCode.Internal, ex.Message);
            }
        }
    }
}

正常系の場合、ステータスコードは StatusCode.OK が戻されます。また、サーバー側で不意のエラーが発生した場合は適切にハンドリングされて StatusCode.Unknown が返されます。

public async UnaryResult<Nil> Sample()
    => throw new Exception("エラーだよ ☆(ゝω・)vキャピ");

// Status(StatusCode=Unknown, Detail="Exception was thrown by handler.")

クライアント側でステータスコードを取得する

サーバーから送信されてきたステータスコードをクライアントで取得する場合は、以下のように GetStatus メソッドを呼び出します。

using System;
using System.Threading.Tasks;
using Grpc.Core;
using MagicOnion.Client;
using MagicOnionSample.ServiceDefinition;

namespace MagicOnionSample.Client
{
    class Program
    {
        static void Main() => MainAsync().Wait();

        static async Task MainAsync()
        {
            var channel = new Channel("localhost", 12345, ChannelCredentials.Insecure);
            var client = MagicOnionClient.Create<ISampleApi>(channel);

            //--- 重要 : ヘッダーを取得してからステータスコードを読む
            var call = client.Sample();
            var header = await call.ResponseHeadersAsync;
            var status = call.GetStatus();

            Console.WriteLine(status);
            // Status(StatusCode=Internal, Detail="エラーだよ☆(ゝω・)vキャピ")

            Console.ReadLine();
        }
    }
}

重要なポイントは ResponseHeadersAsyncawait してから GetStatus をする必要があるということです。ResponseHeadersAsync を呼び出すことなしに GetStatus を呼び出すと例外が発生します。また、OK 以外のステータスコードが送られているレスポンスに対して値を読み出そうとしても例外が発生します。

try
{
    var result = await client.Sample();

    //--- 上の書き方は以下のショートカット記法
    //var call = client.Sample();
    //var result = await call.ResponseAsync;
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    // Status(StatusCode=Internal, Detail="エラーだよ☆(ゝω・)vキャピ")
}

ステータスコードの種類

gRPC が提供している定義済みステータスコードは (執筆時点で) 全 17 種類あります。

コード 説明
OK 0 正常終了
Cancelled 1 操作がキャンセルされた
(通常、クライアント側からの要求で)
Unknown 2 不明なエラー
InvalidArgument 3 クライアント側が不適切な引数を与えた
DeadlineExceeded 4 操作完了前に期限切れ
NotFound 5 要求されたものが見つからなかった
AlreadyExists 6 作成しようとしたものが既に存在する
PermissionDenied 7 操作を実行する権限がない
ResourceExhausted 8 リソースが使い果たされている / 容量不足
FailedPrecondition 9 操作が実行可能な状態にないため拒否された
Aborted 10 中止された
OutOfRange 11 有効な範囲を超えて操作しようとした
Unimplemented 12 指定された操作が未実装 / 未サポート
Internal 13 内部的なエラー
Unavailable 14 現在サービスを利用できない
DataLoss 15 回復不能なデータの損失または破損
Unauthenticated 16 有効な認証資格がない

もちろんこれらは定義済みというだけなので、それ以外の独自コードを定義して送信することも可能です。ReturnStatusCode メソッドの引数が int になっているのはそのためです。

また、gRPC のフレームワークが返す可能性のあるステータスコードも以下にまとまっています。もし見たことのないステータスコードを目撃したら、gRPC 自体が原因かどうかを調べるために使ってみてください。