grpc-gatewayでレスポンスのHTTPステータスコードを指定する
grpc-gatewayを経由して返却されるレスポンスのHTTPステータスコード指定する方法を備忘がてら書く。
ソースはこちら
status.Status
HTTPレスポンスのステータスコードに相当するgrpcのステータスコードがこちらで定義されている。
grpc-gatewayではgrpcサーバが返却したステータスコードをHTTPレスポンスのステータスコードに変換してクライアントに返してくれるので、grpcサーバ側に上記のステータスを意識した実装をすれば良い。
具体的には実装したサービスが返却するerrorインターフェースにgrpc/status.Status型が実装しているErr()をセットする。その際、status.Statusに上述したエラーコードを指定してやる。
NotFoundを返却したい場合は以下を返却してやる。
status.New(codes.NotFound, "error message").Err()
このcodes.NotFound
がステータスコードに該当するので、grpcのステータスコード定義を参考に返却したいコードを指定してやれば良い。
ちなみにstatus.Newでステータスコードを生成せずに、他のerrorインタフェースを返却すると500が返却される。
以下は今回実装したサンプルの説明と実際に動かす手順を記す。
サンプル実装ではREST APIで指定されたIDが見つからなかった場合に404 NotFoundを返却するというものを想定して実装した。
protocのインストールが事前に必要だがその辺の手順は省く。
protoファイル
google/api/annotations.proto
をprotoでimportしてやる必要がある。実態はprotocコマンドのオプションでパスを指定することで認識してくれる(実際のファイルを手元に落としてきてprotoファイル側で格納先パスをimportしても動く)
またRESTのエンドポイントをprotoで記載する必要がある。
今回サンプル的に以下のprotoファイルを作成した。
schema/sample.proto
syntax = "proto3"; package pb; import "google/api/annotations.proto"; service Sample { rpc GetSample (GetSampleRequest) returns (GetSampleResponse) { option (google.api.http) = { get: "/v1/sample/{id}" }; }; } message GetSampleRequest { int64 id = 1; } message GetSampleResponse { int64 id = 1; string name = 2; }
grpc/grpc-gatewayのコード生成
protocによる自動生成を実行する。
grpc-gateway用にも生成が必要なので注意。
$ cd schema // grpcのコード生成 $ protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=plugins=grpc:../service/pb sample.proto // grpc-gatewayのコード生成 $ protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:../service/pb sample.proto
サーバ起動
go build
してgrpcサーバとgrpc gatewayサーバを起動する。
grpcサーバ
$ go build $ ./sample-grpc-gateway-http-response
grpc gatewayサーバ
$ cd gateway $ go build $ ./gateway
それぞれのサーバが起動している状態で/v1/sample/{id}
に対してGETしてみる。
$curl -D - -X GET 'http://localhost:2008/v1/sample/1' HTTP/1.1 404 Not Found Content-Type: application/json Date: Sun, 04 Apr 2021 05:19:42 GMT Content-Length: 66 {"error":"sample not found","code":5,"message":"sample not found"}
404が返ってきた。
エラー実装
サービス側の実装例として、データを取得できなかった際に404を返却する例を示す。
func (s *SampleService) GetSample(ctx context.Context, params *pb.GetSampleRequest) (*pb.GetSampleResponse, error) { data, err := getSampleData(ctx, params.Id) if err != nil { return nil, err } if data == nil { return nil, status.New(codes.NotFound, "sample not found").Err() } return &pb.GetSampleResponse{ Id: data.Id, Name: data.Name, }, nil }
上記のstatus.New(codes.NotFound, "sample not found").Err()
のstatus.New()
の第一引数でステータスコードを指定するだけであとはgrpc gatewayが対応したHTTPステータスコードを返却してくれる。