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(); } } }
重要なポイントは ResponseHeadersAsync
を await
してから 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 自体が原因かどうかを調べるために使ってみてください。