grpc-webを使うための準備

## はじめに
こんにちは。こちらは[トレタ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/toreta) 22日の記事です。(大幅遅刻で申し訳ありません)
トレタでは積極的にgRPCを採用しています。そこでgrpc-webを利用するための準備をまとめました。

## gRPCとブラウザの関係
現在2019/12時点ではウェブブラウザではgRPCで通信することはできません。
そのため、gRPCとhttpを変換するための機構が必要となります。
候補として2つあり、
・ gRPCサービスにRestful-APIのリバースプロキシを追加する[grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)
・ http上でbinaryもしくはbase64encodeしたデータを送信して、envoyなどのproxyでgRPCに変換する[grpc-web](https://github.com/grpc/grpc-web)

## grpc-webとjavascript
grpc-webは`gRPC for Web Clients`と謳われているようにgRPC自体をweb clientで扱うためにあります。
gRPCで利用されるデータのインターフェース構造は`protocol buffers`で設定します。
protocol buffersはIDLのため各種言語用に変換をしなくてはいけません。

## protocとprotoc-gen-grpc-web
protocol buffersを変換するための`protoc`とgrpc-web用に生成するためのプラグイン`protoc-gen-grpc-web`を利用します。
こちら両方とも実行ファイルのためプラットフォーム別にファイルが異なっているので、管理上dockerで生成すると便利だと思います。

```Dockerfile
FROM node:12-buster

ARG APP_HOME=${APP_HOME:-/app}
RUN mkdir -p $APP_HOME

ARG GRPC_HOME=${GRPC_HOME:-/grpc}
RUN mkdir -p $GRPC_HOME
WORKDIR $GRPC_HOME

USER root

RUN npm i rimraf -g
RUN curl -L -O https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip
RUN curl -L -O https://github.com/grpc/grpc-web/releases/download/1.0.7/protoc-gen-grpc-web-1.0.7-linux-x86_64
RUN unzip protoc-3.11.2-linux-x86_64.zip && cp ./bin/protoc /usr/local/bin/. && chmod +x /usr/local/bin/protoc
RUN cp protoc-gen-grpc-web-1.0.7-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web && chmod +x /usr/local/bin/protoc-gen-grpc-web

WORKDIR $APP_HOME
```
protocのdockerをdocker-composeから利用します。

```yml
version: '3.7'
services:
  protoc:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - type: bind
        source: ./
        target: /app
    command: bash -c 'npm run protoc'
```

プロジェクトrootをbindしてnpmで実行するようにpackage.jsonにscriptを作成します。

```json
"scripts": {
    "protoc": "npx rimraf ./grpc && mkdir -p ./grpc && cd ./proto && protoc *.proto -I. --js_out=import_style=commonjs:../grpc --grpc-web_out=import_style=typescript,mode=grpcwebtext:../grpc"
}
```

protocol buffersをprotoディレクトリに用意します、

```proto
syntax = "proto3";

message HelloRequest { string world = 1; }
message HelloResponse { string world = 1; }

service HelloService { rpc World(HelloRequest) returns (HelloResponse); }
```

これで`docker-compose run protoc`と実行すればgrpcディレクトリにjavascriptとtypescriptのファイルが生成されます。

```
.
├── Dockerfile
├── docker-compose.yml
├── grpc
│   ├── HelloServiceClientPb.ts
│   ├── hello_pb.d.ts
│   └── hello_pb.js
├── index.ts
├── node_modules
│   └── grpc-web
│       ├── README.md
│       ├── index.d.ts
│       ├── index.js
│       └── package.json
├── package-lock.json
├── package.json
└── proto
    └── hello.proto
```

## 利用方法
まずはgrpc-webをインストールします。

```shell
npm install -S grpc-web
```

まずは、service clientを呼び出します。

```typescript
const client = new HelloServiceClient("https://gprc-server");
```

次にリクエストを作成します。

リクエストにはprotoで設定したworldにstringで設定します。

```typescript
  const req = new HelloRequest();
  req.setWorld("world");
```

service HelloServiceで設定したrpc Worldにリクエストとmeta dataを渡せばcallbackで返ってきます。

```typescript
client.world(req, {helloAuth: 'secret string'}, (err, res) => {
    if (err) {
      console.error(err.message);
    } else {
      console.log(res.getWorld());
    }
  });
```

これで、クライアント側でgrpcを利用することができました。

## さいごに
protocol buffersからブラウザで通信できるまでをやってみました。
間違っているところやもっといい方法がありましたらご指摘などお待ちしております。

まだenvoyの設定やgRPCサーバの用意などやることはあります。
こちらもご要望がありましたらまた別途かけたらなと思います。
ありがとうございました。