こちらのガイド【gRPC-Web Hello World Guide】を試してみた備忘録となります。
この記事にあるコードもほぼ参考サイトを流用させていただいております。
間違いなどありましたら指摘お願いします。
1. 試した環境
- macOS Mojave
 - Docker version 18.09.2, build 6247962
 - protoc : libprotoc 3.7.1
 - protoc-gen-grpc-web-1.0.4
 - node : v10.14.1
 - Google Chrome : 75.0.3770.100 (Official Build) (64-bit)
 
2. Protocol Buffersの定義ファイルを作成
gRPCのserviceを定義。
あとでこの.protoファイルから.jsファイルを生成します。
syntax = "proto3";
package helloworld;
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
  string name = 1;
}
message HelloReply {
  string message = 1;
}
3. gRPCサービスの実装
nodeで作成
const PROTO_PATH = __dirname + '/helloworld.proto';
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {keepCase: true,
     longs: String,
     enums: String,
     defaults: true,
     oneofs: true
    });
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const helloworld = protoDescriptor.helloworld;
function doSayHello(call, callback) {
  callback(null, {
    message: 'Hello! ' + call.request.name
  });
}
function getServer() {
  const server = new grpc.Server();
  server.addService(helloworld.Greeter.service, {
    sayHello: doSayHello,
  });
  return server;
}
if (require.main === module) {
  const server = getServer();
  server.bind('0.0.0.0:9090', grpc.ServerCredentials.createInsecure());
  server.start();
}
exports.getServer = getServer;
4. プロキシの設定
Envoy Proxyを使います。
admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: greeter_service
                  max_grpc_timeout: 0s
              cors:
                allow_origin:
                - "*"
                allow_methods: GET, PUT, DELETE, POST, OPTIONS
                allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
                max_age: "1728000"
                expose_headers: custom-header-1,grpc-status,grpc-message
                # enabled: true #201912追記 ここの設定が削除されていました
          http_filters:
          - name: envoy.grpc_web
          - name: envoy.cors
          - name: envoy.router
  clusters:
  - name: greeter_service
    connect_timeout: 0.25s
    type: logical_dns
    http2_protocol_options: {}
    lb_policy: round_robin
    # win/mac hosts: Use address: host.docker.internal instead of address: localhost in the line below
    hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}]
FROM envoyproxy/envoy:latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
5. クライアントのコードを作成
5-1. client.js
const {HelloRequest, HelloReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');
const client = new GreeterClient('http://localhost:8080');
const request = new HelloRequest();
request.setName('World');
client.sayHello(request, {}, (err, response) => {
  console.log(response.getMessage());
});
helloworld_pb.jsとhelloworld_grpc_web_pb.jsはhelloworld.protoから自動生成されます。
5-2. package.json
{
  "name": "grpc-web-simple-example",
  "version": "0.1.0",
  "description": "gRPC-Web simple example",
  "devDependencies": {
    "@grpc/proto-loader": "^0.3.0",
    "google-protobuf": "^3.6.1",
    "grpc": "^1.15.0",
    "grpc-web": "^1.0.0",
    "webpack": "^4.16.5",
    "webpack-cli": "^3.1.0"
  }
}
5-3. index.html
<!DOCTYPE html>
<meta charset="UTF-8">
<title>gRPC-Web Example</title>
<script src="dist/main.js"></script>
6. protocから.jsファイルを生成する
.protoファイルの定義からjsファイルを作成するためには、protocとprotoc-gen-grpc-webが必要
6-1. protocをインストール
こちらを参考にインストールしました
http://google.github.io/proto-lens/installing-protoc.html
macで試したのでbrewでインストールしました。
$ brew install protobuf
$ which protoc #インストールされたか確認
/usr/local/bin/protoc
6-2. protoc-gen-grpc-webをダウンロード
こちらのリンクからmac用のprotoc-gen-grpc-web-1.0.4-darwin-x86_64をダウンロードさせていただきました。
ダウンロードしたらPATHの通っているフォルダに移動させる
$ sudo mv ~/Downloads/protoc-gen-grpc-web-1.0.4-darwin-x86_64 \
/usr/local/bin/protoc-gen-grpc-web
chmod +x /usr/local/bin/protoc-gen-grpc-web
これでprotocとprotoc-gen-grpc-webが使える状態になったので、以下のコマンドでjsファイルを生成する
$ protoc -I=. helloworld.proto \
  --js_out=import_style=commonjs:. \
  --grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
helloworld.protoからhelloworld_pb.jsとhelloworld_grpc_web_pb.jsが自動生成されたことを確認しました。
$ ls -l helloworld*
-rw-r--r--  1 kengookumura  staff   1084 Jun 30 15:19 helloworld.proto
-rw-r--r--  1 kengookumura  staff   6645 Jun 30 16:45 helloworld_grpc_web_pb.js
-rw-r--r--  1 kengookumura  staff  14533 Jun 30 16:45 helloworld_pb.js
7. jsファイルをwebpackでバンドルする
yarn
yarn webpack client.js --mode development
# または、
npm i
npx webpack client.js --mode development
dist/main.jsが作られていればOK
8. サンプルを実行
ここまでで実行の準備ができましたので、実際に動かしてみます。
server.js実行
node server.js #ポート9090でリッスンします
Envoyプロキシを実行
docker build -t helloworld/envoy -f ./envoy.Dockerfile .
docker run -d -p 8080:8080 helloworld/envoy
index.htmlを表示するためのwebサーバーをなんでもいいので起動する
# nodeで簡易サーバ
yarn add -D node-static
yarn static -p 8081
# phpで簡易サーバ
php -S 0.0.0.0:8081
# pythonで簡易サーバ
python3 -m http.server 8081
Hello! Worldとコンソールに出ていたら成功しています。
Readmeの手順にそって行いましたが、参考にさせていただいたコードの方では、Hello! Worldの出力部分以外の処理もあり、
https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/helloworld/server.js
https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/helloworld/client.js
Hey! World0〜Hey! World4の部分で繰り返し処理のサンプル。
Got error, ...の部分でエラー処理のサンプルのようですので、こちも参考にすると良いと思いました。
gRPC-Webのchrome拡張も試してみた
こちらを試してみました
【gRPC-Web Developer Tools - Chrome Web Store】
githubはこちら
【SafetyCulture/grpc-web-devtools】
const {HelloRequest, HelloReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');
const enableDevTools = window.__GRPCWEB_DEVTOOLS__ || (() => {});
const client = new GreeterClient('http://localhost:8080');
enableDevTools([
    client,
]);
const request = new HelloRequest();
request.setName('World');
client.sayHello(request, {}, (err, response) => {
  console.log(response.getMessage());
});
// ここの部分を追加、更新しています
const enableDevTools = window.__GRPCWEB_DEVTOOLS__ || (() => {});
const client = new GreeterClient('http://localhost:8080');
enableDevTools([
    client,
]);
このように、helloworld.protoで定義した内容に対して、値がわかりやすいように表示してくれました。
最後まで読んでいただいてありがとうございました。
