pythonでgRPC　チュートリアルチャレンジ

goでgRPCはやったことあったけどpythonではどうやるんだろうーと思ったので、[チュートリアル](https://grpc.io/docs/tutorials/basic/python.html)にチャレンジin mac
goのクイックスタートをやってる方もいてとても分かりやすくまとまっています
https://budougumi0617.github.io/2018/01/01/hello-grpc-go/
間違いあったら指摘いただけると助かります
## インストール
```sh
# grpcのインストール
pip install --upgrade pip
sudo python -m pip install grpcio
sudo python -m pip install grpcio-tools
```

```sh
# サンプルコードのインストール
git clone -b v1.18.0 https://github.com/grpc/grpc
```

動くことを確認

```sh
cd grpc/examples/python/helloworld
python greeter_server.py
# 別ターミナルで
python greeter_client.py

Greeter client received: Hello, you!
# 出たね
```

## protoファイルからスクリプトを生成
protoファイルを作る

- [protoとは](https://developers.google.com/protocol-buffers/docs/proto3)
- [素敵な和訳](https://qiita.com/CyLomw/items/9aa4551bd6bb9c0818b6)
- [type一覧](https://developers.google.com/protocol-buffers/docs/proto3#scalar)
- [pythonで使えるtype一覧（日本語訳）](https://qiita.com/yukina-ge/items/98fe190cef2024d45fbd#python)

```proto
// vim my_if.proto

// なにかをするservice（xx_pb2_proto.pyのコメントとして使用される）
service MyGrpc {
    // なにかを受け取るRPC（xx_pb2_proto.pyのコメントとして使用される）
    rpc GetSomething (MyReq) returns (MyResp) {}
}

message MyReq {
    int32 int_param = 1;
    string str_param = 2;
}

message MyResp {
    int32 status = 1;
    string message = 2;
}   
```

コマンドで生成

```sh
python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. ./my_if.proto
```

（いちいちコマンド打つのは面倒いので[こちら](https://qiita.com/shwld/items/c890a4681697ec84cd8c)の記事を参考に設定ファイルで作成するようにしました）

`my_if_pb2.py`と`my_if_pb2_grpc.py`が生成される。（`pb2`の`2`はProtocol Buffers Python APIのバージョンを示していて`1`と互換性がない）
内容は以下：
>
- classes for the messages defined in route_guide.proto
- classes for the service defined in route_guide.proto
    - RouteGuideStub, which can be used by clients to invoke RouteGuide RPCs
    - RouteGuideServicer, which defines the interface for implementations of the RouteGuide service
- a function for the service defined in route_guide.proto
    - add_RouteGuideServicer_to_server, which adds a RouteGuideServicer to a grpc.Server

自分用に読み換えると以下のような雰囲気（たぶん）

- messages用クラス
- service用クラス
    - `MyGrpcStub`はMyGrpcを呼び出すのに使用するクライアント
    - `MyGrpcServicer`はserviceを実装する用のインターフェース
- service用のfunc
    - `add_MyGrpcServicer_to_server`はgrpcサーバに`MyGrpcServicer`を追加する

とりあえずインストールからスクリプト生成まで完了

## サーバを作ろう
[サンプル](https://github.com/grpc/grpc/blob/master/examples/python/helloworld/greeter_server.py)を見るのが分かりやすい。
生成された[xx_grpc.py](https://github.com/sato9/grpc_practice/blob/master/proto/my_if_pb2_grpc.py)とかを見ながら実装します。全ソースは[github](https://github.com/sato9/grpc_practice)に。

```py
# server.py

from concurrent import futures
import time

import grpc

from proto import my_if_pb2
from proto import my_if_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24


# my_if_pb2_grpcのMyGrpcServicer()を実装
class MyGrpc(my_if_pb2_grpc.MyGrpcServicer):

    def GetSomething(self, request, context):

        # いろんな処理はここで

        print("Someone requested something!")
        # データ取得は`request.xxx`でできて分かりやすい
        print(f'int_param: {request.int_param}')
        print(f'str_param: {request.str_param}')

        # 必ず設定したresponseを返却する
        response = my_if_pb2.MyResp(status=200, message="Great message.")
        return response


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    # MyGrpc()を使うよ！と登録しているかんじ
    my_if_pb2_grpc.add_MyGrpcServicer_to_server(MyGrpc(), server)
    # portの設定
    server.add_insecure_port('[::]:50051')
    server.start()
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        server.stop(0)


if __name__ == '__main__':
    print("start")
    serve()
```
サンプルとだいたい一緒になりました（シンプルなことしかしてないので当然だけど）

```
/code/grpc_practice
❯ python server.py
start
```
疎通確認できてませんがとりあえず起動はおーけー

## クライアントを作ろう
``` py
import grpc

from proto import my_if_pb2
from proto import my_if_pb2_grpc


def run():
    # SSL/TLS認証を利用する場合は`secure_channel`を使用する
    # 通信先を設定する（server.pyでポートを50051に設定したのでそれを指定）
    # リトライ設定等はoptionで指定できる
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = my_if_pb2_grpc.MyGrpcStub(channel)
        # ここでデータを送信している
        resp = stub.GetSomething(my_if_pb2.MyReq(int_param=99, str_param='me'))
    # 受け取ったデータはxx.yyで受け取れる
    print(f'client received: status={resp.status}, message={resp.message}')


if __name__ == '__main__':
    print("run")
    run()
```
簡単です！

## 実際に送信してみる

1.　 まずサーバを実行して待っていてもらう
2.　 クライアントを実行

```sh
/code/grpc_practice
❯ python client.py
run
client received: status=200, message=Great message.
# サーバで設定していたstatusとmessageを受け取れました
```

3.　 サーバのログを確認

```sh
/code/grpc_practice
❯ python server.py
start
Someone requested something!
int_param: 99
str_param: me
# クライアントから送信したパラメータを受け取れました
```
できましたー