サーバーで発生した例外はクライアントに RpcException として通知される。
基本の動作
// サーバー側 public class SampleService : ServiceBase<ISample>, ISample { public async UnaryResult<int> SumAsync(int x, int y) { throw new NotImplementedException("oh!"); // 例外を返す } } // クライアント側 public async Task Run() { try { GrpcChannel channel = CreateChannel(); var client = MagicOnionClient.Create<ISample>(channel); // awaitすると結果がそのまま戻り値として取れる var result = await client.SumAsync(10, 20); // ★この行でGrpc.Core.RpcException発生 Console.WriteLine($"Sum={result}"); // asyncしないとUnaryResultからawaitで結果を取る MagicOnion.UnaryResult<int> ret = client.SumAsync(10, 20); int result2 = await ret.ResponseAsync; // ★この行でGrpc.Core.RpcException発生 Console.WriteLine($"Sum={result2}"); } catch (Exception ex) // ★Grpc.Core.RpcExceptionを受け取る { Console.WriteLine(ex.ToString()); throw; } } // 以下のようなメッセージが表示される // Grpc.Core.RpcException: // Status(StatusCode="Unknown", Detail="Exception was thrown by handler.") // at MagicOnion.Client.ResponseContext`1.Deserialize() // at MagicOnion.UnaryResult`1.UnwrapResponse() // at GrpcClient.Sample.Run() in .\\MagicOnionSample\\GrpcClient\\AppMain.cs
Wait()して終了を同期で待つとSystem.AggregateExceptionとなります。
// 呼び出し側でWait()呼び出すとAggregateExceptionが発生 -> 通常のTask動作 var s = new Sample(); xxxx.Run().Wait(); // ★AggregateException
普通の async/await な Task の動きと同じです。
- 例外は .NET の gRPC 標準の RpcException として扱われる
- サーバーで適切に処理しないと標準の RpcException としてクライアントに返される
- メッセージが「Exception was thrown by handler」で実質無意味
サーバー側で例外を処理してクライアントに返す
.NET WebAPI の ExceptionFilterAttribute みたいなことはできない。各メソッドの catch ブロックに処理を記述する。
// ★サーバー側 public async UnaryResult<int> SumAsync(int x, int y) { try { throw new NotImplementedException("oh!"); } catch (Exception ex) // ★★このブロックを追加 { // InternalはHTTPでは500 Internal Server Error相当 var statusCode = (int)Grpc.Core.StatusCode.Internal; return this.ReturnStatusCode<int>(statusCode, ex.Message); // ★★何か記述する } } // ★クライアント側 //.... catch (Exception ex) // ★Grpc.Core.RpcExceptionを受け取る { Console.WriteLine(ex.ToString()); } // 以下のようにコードとメッセージを受け取る // Grpc.Core.RpcException: // Status(StatusCode="Internal", Detail="oh!") // at MagicOnion.Client.ResponseContext`1.Deserialize() // at MagicOnion.UnaryResult`1.UnwrapResponse() // at GrpcClient.Sample.Run() in .\\MagicOnionSample\\GrpcClient\\AppMain.cs
- statusCode は gRPC のステータスコードを返す
- 数字 + 文字列で返すとクライアントの RpcException に反映される
- そのまま使うと WCF に比べて送信できる情報が貧弱な
もし分散システムで多重転送時してて、メッセージでエラーをスタックして転送するような場合、別途仕組みが必要。どうせクライアントも .NET(というかC#) なので共有プロジェクト配布で良さそう。場合によっては DLL 配布でもよさそう。