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の機能は以下。
最終的に以下のようなフォルダ/ファイルの状態になります。
ここでは、
proxy/Dockerfile
docker-compose.yml
proxy/envoy.yaml
を作成します。
FROM envoyproxy/envoy:v1.15.0
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
version: "3"
services:
envoy:
build:
context: ./proxy
container_name: envoy-grpc-proxy
ports:
- 9000:9000
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
ファイルはバックエンドの定義と同一です。
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
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/
ができます。
最後にsrc/App.js
にgrpcサーバーでリクエストを送るソースコード を追加する。
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
に結果が出力されます。
参考
- 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(ブラウザ-サーバー間)の通信で利用すること。