Envoy Proxyを挟んでgrpc-webから、grpcエンドポイントにアクセスする

grpcについて慣れてきたので、Webクライアントからアクセスすることにします。

https://qiita.com/gp333/items/24733ff4b95d9a4bb977

https://qiita.com/gp333/items/eccd3cee8f705d9ebfb5

- grpcサーバーの前にEnvoyを挟む（Dockerを使う）。
- Reactアプリからgrpcエンドポイントを叩く。
    - [improbable-eng/grpc-web](https://github.com/improbable-eng/grpc-web)を利用。

やっている内容はこの記事と同じ感じです。

https://qiita.com/Le0tk0k/items/af2fbf1d246f0a788ec3

grpc-webは2つ実装があります。

https://syfm.hatenablog.com/entry/2018/12/02/000000

# grpcサーバーの前にEnvoyを挟む

以下の事情があるようです。

> 現状のgRPC-Webの仕様だと、ブラウザから直接gRPCサーバに接続することはできず、プロキシを挟む必要がある

https://qiita.com/aanrii/items/699b4cda0babb3f47a2f#proxy%E3%81%AE%E6%BA%96%E5%82%99%E5%AE%9F%E8%A1%8C

また

> 記事を読めばわかりますが「gRPC-Web app」と「GRPC」の間にはgRPC Web対応のプロキシが必要という事がわかりました。
ブラウザとこのプロキシとはhttp通信を行います。

https://qiita.com/betchi/items/0e9040f21b1f8ecbf321



そこで、調べてみると

> It is currently impossible to implement the HTTP/2 gRPC spec3 in the browser, as there is simply no browser API with enough fine-grained control over the requests. For example: there is no way to force the use of HTTP/2, and even if there was, raw HTTP/2 frames are inaccessible in browsers. 

https://grpc.io/blog/state-of-grpc-web/#the-grpc-web-spec

ということだそうで、gRPCの使用を満たすように実装するにはweb APIが不十分らしいです。

Envoyの機能は以下。

https://qiita.com/kentakozuka/items/b4b7a1686df1e57bc5c5


最終的に以下のようなフォルダ/ファイルの状態になります。

![スクリーンショット 2021-08-23 15.36.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/41b7d972-2844-80e7-f921-f2eed0561a07.png)

ここでは、

- `proxy/Dockerfile`
- `docker-compose.yml`
- `proxy/envoy.yaml`

を作成します。

```Dockerfile:Dockerfile
FROM envoyproxy/envoy:v1.15.0
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
```

```docker-compose.yml
version: "3"
services:
  envoy:
    build:
      context: ./proxy
    container_name: envoy-grpc-proxy
    ports:
      - 9000:9000
```

```yaml:proxy/envoy.yaml
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: 9000 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
                codec_type: auto
                stat_prefix: ingress_http
                access_log:
                  - name: envoy.access_loggers.file
                    typed_config:
                      "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog
                      path: "/dev/stdout"
                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_string_match:
                          - prefix: "*"
                        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
                http_filters:
                  - name: envoy.filters.http.grpc_web
                  - name: envoy.filters.http.cors
                  - name: envoy.filters.http.router
  clusters:
    - name: greeter_service
      connect_timeout: 0.25s
      type: logical_dns
      http2_protocol_options: {}
      lb_policy: round_robin
      dns_lookup_family: V4_ONLY
      upstream_connection_options:
        tcp_keepalive:
          keepalive_time: 300
      load_assignment:
        cluster_name: cluster_0
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: host.docker.internal
                      port_value: 19003

```


以下のコマンドを実行します。


```
go run server/main.go
```

これを放置して、別のプロセスで

```
docker-compose up
```

dockerが起動しているか確認します。

```
docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS          PORTS                                                  NAMES
e0c47f7877a1   cat_envoy   "/docker-entrypoint.…"   45 minutes ago   Up 45 minutes   0.0.0.0:9000->9000/tcp, :::9000->9000/tcp, 10000/tcp   envoy-grpc-proxy
```

grpcurlで叩きます。このときportが9000になっていることに注意しましょう。

```
> grpcurl -plaintext -d '{"target_cat": "tama"}' localhost:9000 cat.Cat/GetMyCat
{
  "name": "tama",
  "kind": "mainecoon"
}
```

## 参考

- https://qiita.com/aanrii/items/699b4cda0babb3f47a2f#proxy%E3%81%AE%E6%BA%96%E5%82%99%E5%AE%9F%E8%A1%8C
- https://zenn.dev/t_horikoshi/articles/dfc2a02c7eb30f7f0819


# Reactアプリからgrpcエンドポイントを叩く

ここからはgrpcのバックエンドの作業とは別になります。

プロジェクトを用意します。

```
yarn create react-app grpc-web-test --typescript
cd grpc-web-test 
yarn add @improbable-eng/grpc-web ts-protoc-gen 
```

- `.proto`ファイルを用意
- `protoc`コマンドでフロントエンド用のgrpcコードを生成
- componentで`grpc-web`を利用

## protoファイル

ルートディレクトリから以下のファイルを作成します。

`cat.proto`ファイルはバックエンドの定義と同一です。

```proto:proto/cat.proto
syntax = "proto3";

option go_package = "./;pb";

package cat;

service Cat {
    rpc GetMyCat (GetMyCatMessage) returns (MyCatResponse) {}
}

message GetMyCatMessage {
    string target_cat = 1;
}

message MyCatResponse {
    string name = 1;
    string kind = 2;
}
```

## protoc実行ファイル

protocはprotobufスキーマから対応するクラスや構造体を生成するものです。

```
touch protoc.sh     
```

```zsh:protoc.sh
set -eu

PROTO_SRC=./proto

PROTO_DEST=./src/proto

mkdir -p ${PROTO_DEST}

PROTOC_GEN_TS_PATH="$(yarn bin)/protoc-gen-ts"

protoc \
    --plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}" \
    --js_out="import_style=commonjs,binary:${PROTO_DEST}" \
    --ts_out="service=true:${PROTO_DEST}" \
    -I ${PROTO_SRC} $(find ${PROTO_SRC} -name "*.proto")
```

これを実行すると、定義ファイルをローカル環境でコンパイルされ、`src/`配下に`proto/`ができます。

![スクリーンショット 2021-08-23 15.45.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/1a0c988a-5f15-c926-13f5-a7d557151e5a.png)

最後に`src/App.js`にgrpcサーバーでリクエストを送るソースコード を追加する。

```src/App.js
import logo from "./logo.svg";
import "./App.css";

const { CatClient } = require("./proto/cat_pb_service");
const { GetMyCatMessage } = require("./proto/cat_pb.js");

var client = new CatClient("http://localhost:9000", null, null);

function App() {
  const callGrpcService = () => {
    const request = new GetMyCatMessage();
    request.setTargetCat("tama");
    client.getMyCat(request, {}, (err, response) => {
      if (response == null) {
        console.log(err);
      } else {
        console.log(response);
      }
    });
  };

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
      <button style={{ padding: 10 }} onClick={callGrpcService}>
        Click for grpc request
      </button>
    </div>
  );
}

export default App;
```

画面は以下のボタンを押すと`console.log`に結果が出力されます。

![スクリーンショット 2021-08-23 15.46.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/2b09375d-4da3-44da-90b9-19a8c9dd1926.png)



## 参考

- https://medium.com/free-code-camp/how-to-use-grpc-web-with-react-1c93feb691b5
- https://qiita.com/aanrii/items/699b4cda0babb3f47a2f#proxy%E3%81%AE%E6%BA%96%E5%82%99%E5%AE%9F%E8%A1%8C

grpc-webについて

gRPC通信をWeb（ブラウザ-サーバー間）の通信で利用すること。


- http://blog.64p.org/entry/2018/10/31/141431
- https://grpc.io/blog/grpc-web-ga/
- https://qiita.com/okmttdhr/items/fb7a8bc0c8afd4e2f4c1