gRPCのサーバー及びクライアントを、Node.jsで実装してみます。
gRPCとは?
gRPCとは、Googleが開発したRPC(remote procedure call:遠隔手続き呼び出し)のフレームワークです。RPC技術によって、別のホストのプログラムによるメソッドを呼び出すなどの処理を行うことができます。
gRPCは、HTTP/2によって通信が行われます。またデータをシリアライズ(シリアル化)するフォーマットとして、デフォルトでProtocol Buffers (通称:Protobuf)が利用されます。つまりこのProtocol Buffers によって言語の形式に依存せずにエンコードされたデータを HTTP/2 上で通信します。その際に利用するのが.protoファイルです。
本記事では、クライアントからサーバーにおいて、1度のリクエストに対して1度のレスポンスを返す Simple RPC を説明します。他にもストリーミングと呼ばれる Server-side streaming RPC や Client-side streaming RPC などの方式があります。
プロジェクトの作成
適当な名前のプロジェクトを作成します。
1 2 |
$ mkdir grpc-sample $ cd grpc-sample |
npmによって初期化を行い、必要なモジュールを追加します。
1 2 |
$ npm init $ npm install grpc @grpc/proto-loader --save |
grpcはNode.jsのgRPC、@grpc/proto-loaderは .protoファイルを取り込むためのモジュールです。
dependencies項目は下記のようになりました。
1 2 3 4 |
"dependencies": { "@grpc/proto-loader": "^0.5.1", "grpc": "^1.21.1" } |
.protoファイル
まず.protoファイルを作成します。名前はhello.protoにしました。この.protoファイルには、サーバー及びクライアントに共通な機能としてのインターフェース定義言語(IDL)を記述していきます。
.protoファイルには大まかに、インターフェイスとMessage(リクエストとレスポンスのメッセージ) を記述していきます。
hello.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
syntax = "proto3"; package hello; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { int32 id = 1; string name = 2; } message HelloReply { string message = 1; } |
1行目
Protocol Buffers のバージョンを指定しています。
2行目
.protoファイルを区別するためのパッケージ名です。
4〜6行目
serviceを定義し、インターフェイスとしてのメソッドを記述していきます。メソッドの引数にリクエスト、戻り値としてレスポンスを設定します。それぞれのリクエスト及びレスポンスは、message型オブジェクトとして記述しておく必要があります(8行目〜、13行目〜)。本記事ではSimple RPCの説明のみですが、例えばServer-side streaming RPCの場合は、下記のようにstreamを付与する必要があります。
1 |
rpc SayHello (HelloRequest) returns (stream HelloReply) {} |
参照ページ
gRPC Basics – Node.js Defining the service
8〜11行目
messageとしてHelloRequestを定義しています。また= 1 や = 2 の様に、そのmessage型オブジェクトの中で一意の数値となるようタグを付与していく必要があります。またint32などのデータ型の種類は、下記参照ページをご覧下さい。
参照ページ
サーバー
次にサーバー側の server.js を作成します。
server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
const grpc = require('grpc') const protoLoader = require('@grpc/proto-loader') const PROTO_PATH = __dirname + '/hello.proto' const packageDefinition = protoLoader.loadSync( PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true } ) const helloProto = grpc.loadPackageDefinition(packageDefinition) const server = new grpc.Server() const sayHello = (call, callback) => { callback(null, { message: 'こんにちわ ID:' + call.request.id + call.request.name }) } server.addService(helloProto.hello.Greeter.service, { sayHello: sayHello }) server.bind('127.0.0.1:50051', grpc.ServerCredentials.createInsecure()) console.log('gRPC server running at http://127.0.0.1:50051') server.start() |
5〜14行目
loadSync()で.protoファイルを読み込んでいます。Node.jsの場合は.protoファイルはコンパイルせずにそのまま読み込みます。keepCaseなどのオプションは下記参照ページをご覧下さい。
参照ページ
起動します。
1 2 |
$ node server.js gRPC server running at http://127.0.0.1:50051 |
※もし@grpc/proto-loaderモジュールを利用しない古いバージョンによる記述だと、起動した場合下記のような注意が表示されます。
1 |
DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition instead |
クライアント
最後に、上記で立ち上げたサーバーに対するクライアント側の client.js を作成します。
client.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
const grpc = require('grpc') const protoLoader = require('@grpc/proto-loader') const PROTO_PATH = __dirname + '/hello.proto' const packageDefinition = protoLoader.loadSync( PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true } ) const helloProto = grpc.loadPackageDefinition(packageDefinition) const client = new helloProto.hello.Greeter('127.0.0.1:50051', grpc.credentials.createInsecure()) client.SayHello({ id: 1, name: '太郎' }, (error, response) => { if (!error) { console.log(response.message) //こんにちわ ID:1太郎 } else { console.error(error) } }) |
起動します。
1 2 |
$ node client.js こんにちわ ID:1太郎 |
参照ページ