gPRCをpythonでやってみた

## はじめに
今回は機会があって、gRPCをPythonでやったので投稿してみることにした。

## gRPCとは？

gRPCとは、Googleによって開発されたオープンソースのRPC(Remote Procedure Call)フレームワームのこと。

公式ページをご覧になりたい方はこちらを参照してください。

<a href="https://grpc.io/" target="_blank">gPRC公式ページ</a>

といっても、RPCという単語にあまり聞きなじみがない方も多いかもしれないので、ちょっとRPCについて、触れていく。


```python
def say_hello():
    print("Hello World!")

def main():
    say_hello()

if __name__ == "__main__":
    main()
```

2つのメソッドがある。
これは、このソースコードを実行した環境で呼び出しから実行まで完結していることがわかる。
つまり、これは以下のように言い換えることができる

> mainメソッドから、say_helloという**Procedure**(関数、メソッド)を**Call**(呼び出し)している

では、このsay_helloメソッドが実行環境とは別のPC、もしくはサーバー上にあった場合どうだろうか？
状況としてはこうなる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301127/b02f6d69-6cbf-c221-4a54-933daefc0609.png)
このように、ローカルとは別のサーバー上の関数(メソッド)を呼び出すことを、

リモート(**Remote**)環境の関数(**Procedure**)を呼び出す(**Call**)ことから、Remote Procedure Call(RPC)と呼ばれる。

それでは、本題。

まず、gRPCの特徴。

## 特徴

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301127/1712ead1-ed88-e777-6446-7c7dff4c394a.png)


- 通信規格は、HTTP/2であること

- protobufと呼ばれる、シリアライズ方式を利用すること

この二つが大きな特徴ある。

### HTTP/2
HTTP/2 を利用することで、以下のような利点がある。

- HTTP/2の特徴であるリクエストとレスポンスの多重化が利用でき、その結果短時間に大量データのやり取りをすることができる

- 高速な双方向のストリーミングが可能 


### protobuf
- データをやり取りするためのスキーマ定義をするフォーマット

- この規格を利用することでAPI仕様を統一することができる


## 基本的な流れ
１．protoファイルを作る

２．proto-toolでコード生成する

３．自動生成されたソースコードを利用して、サービスの実装

４．main関数(エントリポイント)でサービスをgRPCサーバーに登録する

図にすると、このような流れ。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301127/dae41604-1390-f861-093b-24e54650aebe.png)


### 事前準備
PythonでgRPCを扱う場合、以下の２つのライブラリが必要。

- grpcio

- grpcio-tools

以下のコマンドでインストールは可能。

```bash
pip install grpcio
pip install grpcio-tools
```

### １．protoファイルを作る
今回はこのようなprotoファイルにしてみた。

```proto
syntax = "proto3";

package hello;

service SayHello {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}
```

### ２．proto-toolでコード生成する
protoファイルができたので、このあと使うソースコードの生成をしてく。

```bash
python -m grpc_tools.protoc -I../proto/ --pyi_out=./ --python_out=./ --grpc_python_out=./ ./hello.proto
```

このコマンドを実行するか、もしくは以下のようなPythonのコードを実行することでも生成できる。

```python
from grpc.tools import protoc
protoc.main(
    (
        '',
        '-I../proto/', 
        '--pyi_out=./',
        '--python_out=./',
        '--grpc_python_out=./',
        'hello.proto'
    )
)
```

すると、このようなソースファイルが生成されるのて準備完了！

- hello_pb2_grpc.py

- hello_pb2.py

- hello_pb2.pyi


### ３．自動生成されたソースコードを利用して、サーバーの実装
サーバー側の実装をしていく。

サーバー側にはサービスを提供するためのサービサーが必要なので、その用意していく。

```python
import hello_pb2
import hello_pb2_grpc

class SayHelloServicer(hello_pb2_grpc.SayHelloServicer):
    def SayHello(self, request, context):
        return hello_pb2.HelloResponse(message="Hello, %s!" % request.name)
```

### ４．main関数(エントリポイント)でサービスをgRPCサーバーに登録する
ここまで準備したサービスを利用できるようにサーバーにサービスを登録していく。

```python
from concurrent import futures
from hello_pb2_grpc import add_SayHelloServicer_to_server
from hello_servicer import SayHelloServicer
import grpc

def serve():
    port = "50051"
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

    add_SayHelloServicer_to_server(SayHelloServicer(), server)
    server.add_insecure_port('localhost:' + port)
    server.start()
    print('server started..., listening on ' + port)
    server.wait_for_termination()

if __name__ == "__main__":
    serve()
```

ここでは、サーバーは50051番ポートを利用。
次に、このサーバーを利用するために、クライアントを用意すると完成。

クライアント側のソースコードはこれ。

```python
from __future__ import print_function

import grpc
import hello_pb2
import hello_pb2_grpc

def run():
    print("client start...")
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = hello_pb2_grpc.SayHelloStub(channel)
        response = stub.SayHello(hello_pb2.HelloRequest(name="Alice"))
    
    print("received: " + response.message)

if __name__ == "__main__":
    run()
```

### ５．それぞれ起動してみよう
サーバー、クライアントの順で実行していく。

すると、こうなって

サーバー：
```
python3 main.py

server started..., listening on 50051
```

クライアント：
```
python3 main.py

client start...
received: Hello, Alice!
```

クライアントからサーバーへのリクエスト、サーバーからクライアントへのレスポンスの両方が確認できたので、おわり!