Apache APISIX と gRPC-Web の統合
Fei Han
January 25, 2022
gRPC Web 紹介
Googleによって最初に開発されたgRPCは、HTTP/2上に実装された高性能なリモートプロシージャコールフレームワークです。しかし、ブラウザが直接HTTP/2を公開していないため、WebアプリケーションはgRPCを直接使用できません。gRPC Webはこの問題を解決する標準化されたプロトコルです。
最初のgRPC-web実装は2018年にJavaScriptライブラリとしてリリースされ、WebアプリケーションがgRPCサービスと直接通信できるようになりました。その原理は、HTTP/1.1およびHTTP/2と互換性のあるエンドツーエンドのgRPCパイプラインを作成することです。ブラウザは通常のHTTPリクエストを送信し、ブラウザとサーバーの間に位置するgRPC-Webプロキシがリクエストとレスポンスを変換します。gRPCと同様に、gRPC WebはWebクライアントとバックエンドのgRPCサービスの間で事前に定義された契約を使用します。メッセージのシリアライズとエンコードにはProtocol Buffersが使用されます。
gRPC Webを使用すると、ユーザーはブラウザまたはNodeクライアントを使用してバックエンドのgRPCアプリケーションを直接呼び出すことができます。ただし、ブラウザ側でgRPCサービスを呼び出すためにgRPC-Webを使用するにはいくつかの制限があります。
- クライアント側ストリーミングおよび双方向ストリーミング呼び出しはサポートされていません。
- クロスドメインでgRPCサービスを呼び出すには、サーバー側でCORSを設定する必要があります。
- gRPCサーバー側はgRPC-Webをサポートするように設定するか、ブラウザとサーバーの間の呼び出しを変換するためのサードパーティサービスエージェントが利用可能である必要があります。
Apache APISIX gRPC Web プロキシ
Apache APISIXは、プラグインを通じてgRPC Webプロトコルのプロキシをサポートしています。grpc-web
プラグインでは、gRPC WebがgRPC Serverと通信する際のプロトコル変換とデータコーデックの作業が行われ、その通信プロセスは以下の通りです。
gRPC Webクライアント -> Apache APISIX(プロトコル変換 & データコーデック) -> gRPCサーバー
以下は、gRPC Webクライアントを構築し、Apache APISIXを通じてgRPC Webリクエストをプロキシする方法を示す完全な例です。以下の例では、GoをgRPCサーバーのハンドラとして、NodeをgRPC Webクライアントのリクエスタとして使用します。
Protocol Bufferの設定
最初に、Protocol Bufferコンパイラと関連プラグインをインストールします。
-
protoc
とproto-grpc-*
をインストールします。Protocol Bufferコンパイラ
protoc
と、.proto
ファイルからGo、JavaScript、およびgRPC Webインターフェースコードを生成するためのprotoc-gen-go
およびprotoc-gen-grpc-web
プラグインをシステムにインストールする必要があります。以下のスクリプトを実行して上記のコンポーネントをインストールしてください。
#!/usr/bin/env bash set -ex PROTOBUF_VERSION="3.19.0" wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip unzip protoc-${PROTOBUF_VERSION}-linux-x86_64.zip mv bin/protoc /usr/local/bin/protoc mv include/google /usr/local/include/ chmod +x /usr/local/bin/protoc PROTO_GO_PLUGIN_VER="1.2.0" wget https://github.com/grpc/grpc-go/releases/download/cmd/protoc-gen-go-grpc/v${PROTO_GO_PLUGIN_VER}/protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz tar -zxvf protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz mv protoc-gen-go-grpc /usr/local/bin/protoc-gen-go chmod +x /usr/local/bin/protoc-gen-go PROTO_JS_PLUGIN_VER="1.3.0" wget https://github.com/grpc/grpc-web/releases/download/${PROTO_JS_PLUGIN_VER}/protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64 mv protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web chmod +x /usr/local/bin/protoc-gen-grpc-web
-
SayHello
の例となるproto
ファイルを作成します。// a6/echo.proto syntax = "proto3"; package a6; option go_package = "./;a6"; message EchoRequest { string message = 1; } message EchoResponse { string message = 1; } service EchoService { rpc Echo(EchoRequest) returns (EchoResponse); }
サーバー側アプリケーションの設定
-
サーバー側のGo生メッセージとサービス/クライアントスタブを生成します。
protoc -I./a6 echo.proto --go_out=plugins=grpc:./a6
-
サーバー側のハンドラインターフェースを実装します。
// a6/echo.impl.go package a6 import ( "errors" "golang.org/x/net/context" ) type EchoServiceImpl struct { } func (esi *EchoServiceImpl) Echo(ctx context.Context, in *EchoRequest) (*EchoResponse, error) { if len(in.Message) <= 0 { return nil, errors.New("message invalid") } return &EchoResponse{Message: "response: " + in.Message}, nil }
-
サーバー側アプリケーションの実行エントリファイル。
// server.go package main import ( "fmt" "log" "net" "apisix.apache.org/example/a6" "google.golang.org/grpc" ) func main() { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 50001)) if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() a6.RegisterEchoServiceServer(grpcServer, &a6.EchoServiceImpl{}) if err = grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %s", err) } }
-
サーバー側サービスをコンパイルして起動します。
go build -o grpc-server server.go ./grpc-server
クライアントプログラムの設定
-
クライアント側の
proto
コードを生成します。gRPC WebのJavaScript用に、クライアント側のJavaScript生メッセージ、サービス/クライアントスタブ、およびインターフェースコードを生成します。
gRPC Webの
proto
プラグインは、2つのモードのコード生成を提供します。-
mode=grpcwebtext: デフォルトの生成コードは、ペイロードをgrpc-web-text形式で送信します。
-
Content-type: application/grpc-web-text
-
ペイロードはbase64エンコードを使用
-
単項およびサーバーストリーミング呼び出しをサポート
-
mode=grpcweb: ペイロードをバイナリprotobuf形式で送信します。
- Content-type: application/grpc-web+proto
- ペイロードはバイナリprotobuf形式
- 現在は単項呼び出しのみサポート
protoc -I=./a6 echo.proto --js_out=import_style=commonjs:./a6 --grpc-web_out=import_style=commonjs,mode=grpcweb:./a6
-
-
クライアント側の依存関係をインストールします。
npm i grpc-web npm i google-protobuf
-
クライアント側のエントリファイルを実行します。
// client.js const {EchoRequest} = require('./a6/echo_pb'); const {EchoServiceClient} = require('./a6/echo_grpc_web_pb'); // Apache APISIXのエントリに接続 let echoService = new EchoServiceClient('http://127.0.0.1:9080'); let request = new EchoRequest(); request.setMessage("hello") echoService.echo(request, {}, function (err, response) { if (err) { console.log(err.code); console.log(err.message); } else { console.log(response.getMessage()); } });
-
最終的なプロジェクト構造
$ tree . ├── a6 │ ├── echo.impl.go │ ├── echo.pb.go │ ├── echo.proto │ ├── echo_grpc_web_pb.js │ └── echo_pb.js ├── client.js ├── server.go ├── go.mod ├── go.sum ├── package.json └── package-lock.json
上記の手順を完了すると、gRPCサーバーサーバーアプリケーションとgRPC Webクライアントアプリケーションを設定し、サーバーアプリケーションを起動しました。サーバーアプリケーションはポート50001
でリクエストを受信します。
Apache APISIXの設定
次に、Apache APISIXのルーティングプラグイン設定でgrpc-web
プラグインを有効にして、gRPC Webリクエストをプロキシします。
-
grpc-web
プロキシプラグインを有効にします。ルーティングはプレフィックスマッチングを使用する必要があります(例:
/*
または/grpc/example/*
)。gRPC WebクライアントはURIにproto
で宣言されたパッケージ名、サービスインターフェース名、メソッド名などを渡すため(例:/path/a6.EchoService/Echo
)、絶対マッチを使用するとプラグインがURIからproto
情報を抽出できなくなります。$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri":"/*", // プレフィックスマッチングモード "plugins":{ "grpc-web":{} // gRPC Webプラグインを有効化 }, "upstream":{ "scheme":"grpc", "type":"roundrobin", "nodes":{ "127.0.0.1:50001":1 // gRPCサーバーのリスンアドレスとポート } } }'
-
gRPC Webプロキシリクエストを検証します。
Nodeから
client.js
を実行して、gRPC WebプロトコルリクエストをApache APISIXに送信できます。上記のクライアント側とサーバー側の処理ロジックはそれぞれ、クライアントがサーバーに
hello
というメッセージを送信し、サーバーがメッセージを受信してresponse: hello
と応答するものです。実行結果は以下の通りです。$ node client.js response: hello
-
grpc-web
プロキシプラグインを無効にします。ルーティングプラグイン設定からgrpc-web属性を削除するだけです。
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri":"/*", "plugins":{ }, "upstream":{ "scheme":"grpc", "type":"roundrobin", "nodes":{ "127.0.0.1:50001":1 } } }'
まとめ
この記事では、Apache APISIXでgrpc-web
を使用するための実践的な経験を紹介しました。
GitHub Discussionsでディスカッションを開始したり、メーリングリストを通じてコミュニケーションを取ることを自由に行ってください。