gRPCをブラウザで使いたい

引き続きgRPCについて調べている。

2017年11月現在では現状gRPCはそのままブラウザ上で動かすことが出来ない(Node実装はあるけどブラウザでは動かせない)。

現状ブラウザでgRPCを動かしたいと思ったら二つの道があるようだ。

grpc-webを使う

github.com

これはgRPCサーバーの前段にプロキシサーバーを立てることにより、ブラウザ環境でgRPCのクライアントを動かせるようにするというもの。

import {grpc, BrowserHeaders, Code} from "grpc-web-client";

// Import code-generated data structures.
import {BookService} from "../_proto/examplecom/library/book_service_pb_service";
import {QueryBooksRequest, Book, GetBookRequest} from "../_proto/examplecom/library/book_service_pb";

const queryBooksRequest = new QueryBooksRequest();
queryBooksRequest.setAuthorPrefix("Geor");
grpc.invoke(BookService.QueryBooks, {
  request: queryBooksRequest,
  host: "https://example.com",
  onMessage: (message: Book) => {
    console.log("got book: ", message.toObject());
  },
  onEnd: (code: Code, msg: string | undefined, trailers: BrowserHeaders) => {
    if (code == Code.OK) {
      console.log("all ok")
    } else {
      console.log("hit an error", code, msg, trailers);
    }
  }
});

実際に試してみると、確かにブラウザからgRPCが使えた。GitHubを見るとGo前提っぽく感じるけど、grpcwebproxyが単体で提供されているので、例えばバックエンドはGoでなくてもgRPCサーバーの前段でこのプロキシサーバーを起動させてあげれば良い。

grpc-web/go/grpcwebproxy at master · improbable-eng/grpc-web · GitHub

そうなんだけど実際に試してみると、クライアント側でセットしたメタデータがサーバーまでたどり着かない。

github.com

調べてみたら同様の症状の人がIssueを上げているよう。ここで一旦grpc-webは諦めて、次のgprc-gatewayの方を触ってみた。

grpc-gatewayを使う

こちらはgRPCのバックエンドサーバーの前段において、RESTのエンドポイントを提供するというもの。grpc-webとは違い、protoファイルにも手を加える必要がある。

 syntax = "proto3";
 package example;
+
+import "google/api/annotations.proto";
+
 message StringMessage {
   string value = 1;
 }
 
 service YourService {
-  rpc Echo(StringMessage) returns (StringMessage) {}
+  rpc Echo(StringMessage) returns (StringMessage) {
+    option (google.api.http) = {
+      post: "/v1/example/echo"
+      body: "*"
+    };
+  }
 }

grpc-gateway は、RESTのエンドポイントを作るための実装だけではなく、swagger定義も出力出来るようになっている。なので、gRPCが使える環境の場合には普通にprotoファイルから生成したクライアントを使い、RESTを使う必要がある場合にはgatewayの出力したswagger定義からRESTクライアントを生成すればよい。

protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --swagger_out=logtostderr=true:. \
  path/to/your_service.proto

まとめ

grpc-web の方がprotoファイルに余計なものを書かなくてすむので後々はこっちの方向性かなーと思った。でも、現状だとまだ grpc-gateway を使った方が良いのかなーという感触。