gRPCでElixirとPython間通信をする

# 1.はじめに

Elixir～Python間を、gRPCを使って異言語間通信を試してみます。

今回の記事では、

- gRPCサーバ：Elixir
- gRPCクライアント：Python3

としています。

##実行環境

|||
|:--|:--|
|ハード|Raspberry Pi 3B+|
|OS|Raspbian Buster, Ubuntu Server 20.04LTS|
|Python|3.7.3|
|Elixir|1.7.4 (compiled with Erlang/OTP 21)|
※いづれもaptパッケージでインストールできるもの

# 2.Elixir側

最初に、Elixir側の準備をします。

ここでは、[elixir-grpcのライブラリ](https://github.com/elixir-grpc/grpc)に含まれているサンプル「[examples/helloworld](https://github.com/elixir-grpc/grpc/tree/master/examples/helloworld)」を使ってみます。

クライアントからnameに名前を代入してリクエストすると、サーバからは"Hello <name>"という文字列が帰ってきます。

※詳しい手順は別記事にまとめてますので、[こちら](https://hotch-potch.hatenadiary.jp/entry/2020/05/11/200000)をご覧下さい。

```sh:コマンドライン
$ pwd
..../elixir/
#クローンします
$ git clone https://github.com/elixir-grpc/grpc.git
#サンプルディレクトリに移動して
$ cd ./grpc/examples/helloworld/
#依存関係の処理、コンパイルします。
$ mix do deps.get, compile
```

# 2.Python側

次はPython側でクライアント・サーバを作る手順を示します。

##(1)grpcツールのインストール

```sh:コマンドライン
$ pip3 install grpcio grpcio-tools protobuf
```


##(2)インターフェースの生成

protoファイルを解析して、Python向けのインターフェースを生成するツールを準備します。

```sh:コマンドライン
$ pwd
..../elixir/grpc/examples/helloworld
#Pythonのスクリプトを保存するフォルダを作成
$ mkdir python
$ cd python
#elixirのほうで定義している、protoファイルをコピー
python $ cp ../priv/protos/helloworld.proto ./
#ツールのファイルを生成
python $ touch codegen.py
python $ chmod 755 codegen.py
```


Python向けのインターフェースを生成するツールのソースコードです。
今回は`helloworld.proto`を対象にしてるので、コード中では`__NAME="helloworld"`としています。

```python:codegen.py
#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-
"""
protoファイルのコンパイル

以下の2つのファイルが生成されます。
・*_pb2.py      : シリアライズのインターフェース
・*_pb2_grpc.py : gRPCのインターフェース
"""
from grpc.tools import protoc

#protoのファイル名（の名前部分）
__NAME="helloworld"

#Python向けのインターフェースを生成
protoc.main(
    (
        '',
        '-I.',
        '--python_out=.',
        '--grpc_python_out=.',
        ('{}.proto'.format(__NAME)),
    )
)
```

Python向けのインターフェースを生成します。

```sh:コマンドライン
python $ ./codegen.py
python $ ls
codegen.py  helloworld_pb2_grpc.py  helloworld_pb2.py  helloworld.proto
```

`*_pb2.py`と、`*_pb2_grpc.py`の二つのファイルが生成されました。

##(3)Python側クライアントの作成

###クライアントのスクリプトを作成

```sh:コマンドライン
#ファイルを生成
python $ touch client.py
python $ chmod 755 client.py
```

```python:client.py
#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-
"""
gRPCクライアント
by myasu 2020 
"""

import sys
import grpc
# 先ほどprotoから生成したインターフェースをインポート
import helloworld_pb2
import helloworld_pb2_grpc


def grpc_client(request):
    """gRPCクライアントの通信処理
    Parameters
    ----------
    request : string
        gRPCサーバに送るメッセージ
    """
    # Elixir側のgRPCサーバのアドレスとポートを指定して接続
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        # Elixir側のgRPCサーバにrequestする
        response = stub.SayHello(helloworld_pb2.HelloRequest(name=request))

    # responseの内容を確認
    print(' Response:', response.message)


if __name__ == '__main__':
    """メイン処理
    """
    # 引数の読み込み
    args = sys.argv
    # 引数の長さをチェック
    if len(args) == 2:
        # 実行
        grpc_client(args[1])
    else:
        # エラー
        print('Arguments are too short')
```

#### Python→Elixir→Python間の通信テスト

最初にサーバ・Elixir側を起動します。

```sh:ターミナル１・ElixirのgRPCサーバ側
$ mix grpc.server
10:58:01.872 [warn]  cowlib should be >= 2.9.0, it's 2.8.1 now. See grpc's README for details
10:58:01.977 [info]  Running Helloworld.Endpoint with Cowboy using http://0.0.0.0:50051

（・・・ここから先はクライアントの要求があったときに表示・・・）
22:58:04.739 [info]  Handled by Helloworld.Greeter.Server.say_hello
22:58:04.744 [info]  Response :ok in 4ms
22:58:09.237 [info]  Handled by Helloworld.Greeter.Server.say_hello
22:58:09.237 [info]  Response :ok in 15μs
22:58:13.552 [info]  Handled by Helloworld.Greeter.Server.say_hello
22:58:13.552 [info]  Response :ok in 15μs
[Ctrl-\]で停止
```

次にクライアント・Python側を実行します。
スクリプトの引数には、任意のメッセージが指定できます。

```sh:ターミナル２・PythonのgRPCクライアント側
python $ ./client.py chika
 Response: Hello chika
python $ ./client.py you
 Response: Hello you
python $ ./client.py ruby
 Response: Hello ruby
python $ ./client.py CYaRon!
 Response: Hello CYaRon!
```

メッセージの先頭に、サーバ側が"Hello "を付けて返してきます。
このような感じで、異種言語間の通信が出来ました。


#4.その他

##(1)grpc-toolsのインストール方法
20年6月現在、`Raspbian`や`Ubuntu 20.04LTS`のaptで`python3-grpcio python3-grpcio-tools`でインストールすると、下記バージョンが入ります。

||||
|:--|:--|:--|
|python3-grpc-tools | 1.14.1-1build3 | Protobuf code generator for gRPC (Python 3) |
|python3-grpcio | 1.16.1-1ubuntu5 | GRPC system (Python 3) |

`1.23.0`よりも前のバージョンでは、`wait_for_termination`命令がサポートされていませんので、実行時に下記のエラーメッセージが出ます。

```:エラーメッセージ
AttributeError: '_Server' object has no attribute 'wait_for_termination'
```

従って、現時点では`apt`ではなく、`pip3`でインストールして下さい。

参考: https://github.com/grpc/grpc/issues/20333

#5.参考資料

- https://github.com/elixir-grpc/grpc
- https://qiita.com/ike_dai/items/1a28f6a42c4a0d3a49d0