Cloud EndpointsでgRPCのエラー詳細をレスポンスに含める

TL;DR

Google Cloud Endpointsを利用してgRPCサーバーをHTTP JSON APIサーバーに変換する場合で、エラー表現にRicher error modelを利用するケースにおいては google/rpc/error_details.proto を proto descriptor setに含める(Protocol Buffer ファイルから import する)ことで、エラー詳細がレスポンスペイロードにJSONオブジェクトとして返却されるようになります。

背景

Google Cloud Endpointsを利用すると、gRPCで実装されたサーバーを一般的なHTTP JSON APIサーバーに変換することができます。 この変換は、Extensible Service Proxy V2(ESPv2) と呼ばれる Envoy ベースのゲートウェイサーバーを利用して行います。 ESPv2では、Envoy の gRPC-JSON transcoder 機能を用いて変換処理を行っています。

また、一般にgRPCを利用されたサーバーでエラーを表現する場合、「Standard error model」と「Richer error model」という2つの方法が知られています。 Standard error modelは単純なgRPC ステータスコードを利用してエラーを表現する方法です。 Richer error modelは、事前にエラー詳細のフォーマットを規定しておき、メタデータにその情報を詰めてレスポンスすることで、ステータスコードよりも多くのエラー情報を表現することができる方法です。 例えば、GoogleのAPI設計ガイドのエラーでは、Google APIs でのRicher error modelの適用例を知ることができます。

問題

前述の通り、Richer error modelではエラー詳細をメタデータに詰めてレスポンスします。 一方で、一般的なHTTP JSON APIサーバーでのエラーの表現方法は、レスポンスペイロードにエラーを表現するJSONオブジェクトを詰めて返却することです。 よって、何も工夫せずにGoogle Cloud Endpointsを利用すると、エラー詳細はレスポンスのHTTPヘッダー grpc-status-details-bin に詰められて返却されていまいます。 そのため、APIクライアントはHTTPヘッダーのエラー詳細をパーズして、エラーハンドリングを行う必要が発生してしまいます。 これを行うライブラリはありますが、余計な手間をAPIクライアントに押し付けることは問題です。

あるべき姿は、通常のHTTP JSON APIサーバーと同様に、エラー詳細をレスポンスペイロードにJSONオブジェクトとして返却することです。

対処方法

EnvoyのgRPC-JSON transcoderには機能フラグconvert_grpc_statusが存在しており、これを有効にするとHTTPヘッダーgrpc-status-details-binに含まれる エラー詳細をデコードし、レスポンスペイロードにJSONオブジェクトを詰めて返却してくれます。この機能を利用することで、問題を解消することができます。また、ESPv2ではこの機能フラグは有効になっています。 しかし、私の環境では何故かこの機能が動作していませんでした。

原因は、proto descriptor setgoogle/rpc/error_details.proto を含めていないことでした。 Envoyの公式ドキュメントには以下の記述があります。

In order to transcode the message, the google.rpc.RequestInfo type from the google/rpc/error_details.proto should be included in the configured proto descriptor set.

よって、このファイルを protoc を実行するプロジェクトに配置した上で、Protocol Buffer ファイルから google/rpc/error_details.proto を import する必要がありました。 上記を実施することで、エラー詳細がレスポンスペイロードにJSONオブジェクトとして返却されるようになりました。