grpc-gateway と使われてるProtocolBuffer周辺技術メモ

grpc-gatewayはHTTP2+ProtocolBuffer をプロトコルに用いるgRPCのサービスを、HTTP/1.1のRESTfulな JSON APIとして利用できるようにするリバースプロキシを生成してくれるツールだ。

厳密にはProtocolBuffersを処理するコマンドであるprotocのプラグインとして動作し、protocに読み込んだgRPCのサービス定義をもとにGoで記述されたコードを生成する。生成されたコードはHTTPサーバのハンドラになっていて、net/httpに登録して使えるようになっている。

ハンドラはHTTP/1.1でリクエストを受け取ると、リクエストに含まれるJSONを対応するProtocolBufferのメッセージに変換し、プロキシ先のgRPCサービスのメソッドを呼び出す。このgRPCサービスは、元にしたスキーマが同じであればGo以外の言語で実装されていても良い。そしてメソッドの呼び出し結果のProtocolBufferメッセージをJSONに変換してレスポンスを作る。

gRPCは利用したいが、既存のシステムから簡単に利用するためにREST APIの口を残したいだとか、管理用のAPI Explorer的なものを作るときにWebブラウザから直接利用できるREST APIがほしいだとか、そういった際に便利なツールである。

くわしくは作者であるyuguiさんの紹介記事やリポジトリのREADMEを読むとわかりやすい。

使い方などは上記の記事を読めば十分なので、この記事ではgrpc-gatewayを利用する過程で僕が学んだ gRPCやProtocolBufferに関する知見をまとめてみようと思う。メモ的なものだけど、役立つ資料へのポインタも貼っておくので何かの参考になればうれしい。

protocとプラグインによるコード生成

grpc-gatewayを利用する際には以下のようなコマンドを実行する。このコマンドの結果としてリバースプロキシのコードが生成される。

protoc -I... -I... --grpc-gateway_out=. path/to/your_service.proto

protocでは、--grpc-gateway_out のようなオプションを指定することでProtocolBufferのスキーマを元にしたコードの生成機能を利用できる。このオプションはプラグインの読み込みの仕組みになっていて、例えば --hogehoge_outのようにオプションをすると、protocがprotoc-gen-hogehogeという名前のコマンドが利用されるようになっている。

プラグインは標準入力からパース済みのProtocolBufferのスキーマ情報を受け取り、その情報を元に何らかの処理を行って、結果として生成するファイルの名前やその内容を標準出力に出力するというインタフェースになっている。標準入出力でやりとりされるデータがProtocolBufferのバイナリになっているのがおもしろい。

詳しい仕組みについては、yuguiさんによる以下の記事が参考になる。

ProtocolBuffer 定義ファイルのoption

grpc-gateway を利用する時使うProtocolBufferのスキーマは、例えば以下のようになる。

syntax = "proto3";
package articles;

import "google/protobuf/empty.proto";
import "google/api/annotations.proto";

// 省略

service ArticlesService {
    rpc Post(PostRequest) returns (PostResponse) {
        option (google.api.http) = {
            post: "/articles/post"
            body: "*"
        };
    };
    rpc Recent(google.protobuf.Empty) returns (RecentResponse) {
        option (google.api.http) = {
            get: "/articles/recent"
        };
    };
}

注目すべきは、optionという文法でメソッド定義に対応するURLのパス情報が記述されている部分だ。

ProtocolBufferのスキーマは基本的にはメッセージやサービス定義を記述する仕組みではあるが、grpc-gatewayのような生成ツールから利用する固有の情報を定義に追加して埋め込むこともできる。その埋め込みのための記法がoptionで、この例のようにメソッド追加の情報を記述する以外にも、幾つかの文法要素に情報を埋め込むことができる。

メソッドに対してoptionに指定できる属性は、MethodOptions型としてあらかじめ定義されいるが、独自にoptionを定義して属性を追加することもできる。option (google.api.http) = ...のような表記はgoogle.apiパッケージにおいて拡張されたhttp optionを設定するといった意味になる。

この話題についてもyuguiさんの記事が参考になりおすすめだ。

ProtocolBuffer と JSONの変換

ProtocolBufferの仕様には、ProtocolBufferとJSONのマッピングが含まれており、ProtocolBufferとJSONの相互変換は基本的に可能である。各言語のProtocolBufferのライブラリにもJSONへの変換機能が含まれていたりする。

golang/protobufを使ってGo向けに生成したProtocolBufferのメッセージ構造体にはJSONへの変換のためのタグが指定されているので、Goの標準のjsonライブラリを使えばProtocolBufferとJSONの相互変換は可能である。ただ一部のデータ型は標準のjsonライブラリを利用するだけでは変換が難しいため、jsonpbというより完全な相互変換が行えるライブラリも存在する。

grpc-gatewayの内部では、ProtocolBufferとJSONの相互変換を行っている。デフォルトではjsonpbを利用するようになっているが、より高速な標準のjsonライブラリを利用する設定にもできる。

このあたりの話題については以下の記事でも詳しい。

感想

grpc-gatewayはツールとして便利なだけでなく、ProtocolBuffer周辺の様々な技術が利用されているので、自然と知識が得られてよかった。あとyuguiさんのgRPCやProtocolBufferに関する資料がとにかく充実していて、技術的背景を理解できるようになって良かったので、一通り読むことをおすすめしたい。

gRPC周辺技術にはそこそこ慣れてきたのと、情報ソースもわかってきたので、ある程度実用的なものを作ってみるのが次のステップのようには思うので少し考えてみる。