概要
- PythonでgRPCのシンプルな処理を作成する。
- RESTのように何らかの値をリクエストして、結果を返す処理を作ってみる。
全体の流れ
- grpcio-toolsのインストール
-
.proto
ファイルを作成して、コンパイルする。 - 自動生成されたソースを使ってserverとclientのソースも作る。
- テスト
1. grpcio-toolsのインストール
pipでインストール
$ pip install grpcio-tools
2. protoファイルの作成
simple.proto
syntax = "proto3";
package simple;
// request
message SimpleRequest{
string name = 1;
string msg = 2;
}
// response
message SimpleResponse{
string reply_msg = 1;
}
// interface
service SimpleService{
rpc SimpleSend (SimpleRequest) returns (SimpleResponse) {}
}
以下のソースを作って実行する。(コンパイル)
同じフォルダにsimple_pb2.py
とsimple_pb2_grpc.py
が作成される。
codegen.py
from grpc.tools import protoc
protoc.main(
(
'',
'-I.',
'--python_out=.',
'--grpc_python_out=.',
'simple.proto',
)
)
実行
$ python codegen.py
3. serverとclientのソース作成
サーバ側
server.py
import grpc
import time
import simple_pb2
import simple_pb2_grpc
from concurrent import futures
class SimpleServiceServicer(simple_pb2_grpc.SimpleServiceServicer):
def __init__(self):
pass
def SimpleSend(self, request, context):
print('logging: name {}, msg {}'.format(request.name, request.msg))
# protoファイルのResponseと一致させる
return simple_pb2.SimpleResponse(
reply_msg = 'Hello! ' + request.name + '. Your message is ' + request.msg
)
# start server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
simple_pb2_grpc.add_SimpleServiceServicer_to_server(SimpleServiceServicer(), server)
server.add_insecure_port('[::]:5051')
server.start()
print('run server')
# wait
try:
while True:
time.sleep(3600)
except KeyboardInterrupt:
# stop server
server.stop(0)
- ソース解説
- クラス名はprotoファイルのservice(SimpleServie)+Servicerと合わせる。
- 関数名(SimpleSend)はrpc と合わせる。関数名なのでPythonならスネークケース(simple_send)が良いかも
-
simple_pb2
にSimpleResponse
が定義されるため、そこからresponseを返す
クライアント側
client.py
import grpc
import simple_pb2
import simple_pb2_grpc
with grpc.insecure_channel('localhost:5051') as channel:
stub = simple_pb2_grpc.SimpleServiceStub(channel)
name = "Tom"
msg = "Test"
response = stub.SimpleSend(simple_pb2.SimpleRequest(name=name, msg=msg))
print('Reply: ', response.reply_msg)
- ソース解説
-
SimpleRequest
は引数としてnameとmsgを取る。それに渡してクラスを生成してるイメージ - stubの
SimpleSend
関数を呼び出して、それに引数として生成したクラスを渡してる - responseに含まれるreply_msgを出力
-
4. テスト
serverの起動
$ python server.py
run server
クライアントの実行
$ python client.py
Reply: Hello! Tom. Your message is Test
Process finished with exit code 0
作業フォルダ(最終形)
C:\Users\grpc のディレクトリ
2020/01/21 17:47 <DIR> .
2020/01/21 17:47 <DIR> ..
2020/01/21 17:45 327 client.py
2020/01/21 14:31 173 codegen.py
2020/01/21 17:47 851 server.py
2020/01/21 17:43 300 simple.proto
2020/01/21 17:43 4,212 simple_pb2.py
2020/01/21 17:43 1,342 simple_pb2_grpc.py
リストやdictを使う場合
リストの値を返したい場合
.protoファイル
syntax = "proto3";
package simple;
// request
message SimpleRequest{
}
// response
message SimpleResponse{
repeated string msgs = 1; // repeatedを使う
}
// interface
service SimpleService{
rpc simple_send (SimpleRequest) returns (SimpleResponse) {}
}
サーバ側
リストを返すサーバ
import grpc
import time
import simple_iter_pb2
import simple_iter_pb2_grpc
from concurrent import futures
class SimpleServiceServicer(simple_iter_pb2_grpc.SimpleServiceServicer):
def __init__(self):
pass
def simple_send(self, request, context):
sample_data = ['message1', 'message2']
return simple_iter_pb2.SimpleResponse(msgs=sample_data) # リスト名を忘れずに
# start server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
simple_iter_pb2_grpc.add_SimpleServiceServicer_to_server(SimpleServiceServicer(), server)
server.add_insecure_port('[::]:5051')
server.start()
print('run server')
# wait
try:
while True:
time.sleep(3600)
except KeyboardInterrupt:
# stop server
server.stop(0)
dictの値を返したい場合
.protoファイル
syntax = "proto3";
package simple;
// request
message SimpleRequest{
}
// response
message SimpleResponse{
map<string, string> msg = 1; // mapを使う
}
// interface
service SimpleService{
rpc simple_send (SimpleRequest) returns (SimpleResponse) {}
}
サーバは省略
参考
以下の記事が大変参考になりました。より詳しい記載があります。