1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-08-23

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

  • grpcサーバーの前にEnvoyを挟む(Dockerを使う)。
  • Reactアプリからgrpcエンドポイントを叩く。

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

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

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

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

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

また

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

そこで、調べてみると

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.

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

Envoyの機能は以下。

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

スクリーンショット 2021-08-23 15.36.49.png

ここでは、

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

を作成します。

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
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"
}

参考

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/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     
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

最後に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

参考

grpc-webについて

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

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?