PythonでシンプルなgRPC

## 概要
- PythonでgRPCのシンプルな処理を作成する。
- RESTのように何らかの値をリクエストして、結果を返す処理を作ってみる。

## 全体の流れ
1. grpcio-toolsのインストール
2. `.proto`ファイルを作成して、コンパイルする。
3. 自動生成されたソースを使ってserverとclientのソースも作る。
4. テスト

## 1. grpcio-toolsのインストール
```bash:pipでインストール
$ pip install grpcio-tools
```

## 2. protoファイルの作成
```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`が作成される。

```python:codegen.py
from grpc.tools import protoc
protoc.main(
    (
        '',
        '-I.',
        '--python_out=.',
        '--grpc_python_out=.',
        'simple.proto',
    )
)
```

```bash:実行
$ python codegen.py
```

## 3. serverとclientのソース作成
サーバ側

```python: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. テスト
```bash:serverの起動
$ python server.py
run server
```

```bash:クライアントの実行
$ 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:.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) {}
}

```

サーバ側

```python:リストを返すサーバ
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:.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) {}
}
```

サーバは省略

## 参考
以下の記事が大変参考になりました。より詳しい記載があります。

- [gRPCとProtocol Buffersによるアプリケーション間通信 / Python](https://note.com/npaka/n/n5c3263ad88ff)
- [PythonでgRPCで双方向通信を試す。](https://qiita.com/ike_dai/items/1a28f6a42c4a0d3a49d0)
- [gRPC(Go) で API を実装する](https://blog.fenrir-inc.com/jp/2016/10/grpc-go.html)