YetAnotherHttpHandlerを利用してUnityでgRPC通信を行う

# はじめに

Cysharpから[YetAnotherHttpHandler](https://github.com/Cysharp/YetAnotherHttpHandler)がリリースされたので、
これを使ってgRPCでの通信ができるかどうか試してみました!

# 0. 実行環境

M1 Mac

```shell-session
$ sw_vers
ProductName:		macOS
ProductVersion:		13.5.2
BuildVersion:		22G91
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15000/123feb14-cb0f-1ecf-f3da-5c5b51809a1f.png" width=240 />

Unityのバージョン
(2021.3以降であれば、YetAnotherHttpHandlerがサポートしているので問題ないと思います)

```shell-session
$ cat ProjectSettings/ProjectVersion.txt
m_EditorVersion: 2022.3.10f1
m_EditorVersionWithRevision: 2022.3.10f1 (ff3792e53c62)
```


# 1. CloudRunの設定 (Server設定)

## 1-1. localでサーバの動作確認

確認用のサーバはGCPのCloud Runで動かします.
公式にhelloworldのサンプルがあるので、これ利用しました.

- https://github.com/grpc/grpc-go/tree/master/examples/helloworld

ただ、Cloud Runで動かすにはhealth checkを通す必要があるので若干の修正が必要です.
上記コードに少しパッチを当てたものをforkして用意しました.

- https://github.com/mattak/grpc-go/tree/cloudrun/examples/helloworld

パッチを当てた内容は下記のような感じです.

- go.modの追加、proto生成後のpackage参照を変更 (ビルド時の依存更新)
- PORTを環境変数で指定可能にした (Cloud Run対応)
- サーバ起動時のコードにhealth checkのコードを追記 (Cloud Run対応)
- リフレクションの導入 (提供するメソッド、型、その他のメタデータを公開)
- Dockerfileの追加 (ArtifactRegistoryへの登録用)

```shell-session
$ git clone https://github.com/mattak/grpc-go.git -b cloudrun --depth 1
$ cd grpc-go/examples/helloworld
```

これをｃloud runで動く形にビルド.

```shell-session
$ docker build --platform linux/amd64 -t grpc_sample -f Dockerfile .
```

試しにlocalで動かしてみて、通信に成功すればOKです!

server

```shell-session
$ docker run --rm -p 50051:50051 -it grpc_sample
2023/09/26 11:06:57 server listening at [::]:50051
```

health check

```shell-session
$ grpcurl -plaintext localhost:50051 grpc.health.v1.Health.Check
{
  "status": "SERVING"
}
```

request check

```shell-session
$ go mod download
$ go run greeter_client/main.go
2023/09/26 20:08:37 Greeting: Hello world
```


## 1-2. Cloud Runにimageアップロードして確認

先程のdocker imageをGCPのArtifact Registoryにpushします.
(事前にGCPのproject作成やartifact registroyへのレポジトリ作成はしておく)

```shell-session
# 定義:
# PROJECT=FIXME
# REPOSITORY=FIXME
# IMAGE=FIXME
# URI=asia-northeast1-docker.pkg.dev/$PROJECT/$REPOSITORY/$IMAGE:latest

docker tag grpc_sample $URI
docker push $URI
```

CloudRunのページからサービスを作成.

- 既存のコンテナイメージから1つのリビジョンをデプロイする (先程のURIを指定)
- リージョン: asia-northeast1
- 認証: 未認証の呼出を許可
- コンテナポート: 50051
- ヘルスチェック
  - 種類: 起動時チェック
  - プローブタイプ: gRPC
  - 初期遅延: 0
- ネットワーキング
  - HTTP/2 エンドツーエンドを使用する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15000/616ee66e-9cea-65f3-8ba5-c45b22f48f36.png" width=480 />

下記のようにヘルスチェックが通っていれば成功です!

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15000/633ed6d3-720f-af27-9051-4b14e1503011.png" width=480 />

ここで表示されるURLはメモっておきます。

# 2. Unityでの設定

## 2-1. プロジェクトの設定

適当にUnityの新規プロジェクトを作成.

YetAnotherHttpHandlerをInstallするが、
デフォルトだとSystem.IO.Pipelineなどがmissingになるので事前に必要となる依存を追加する.

- https://github.com/Cysharp/YetAnotherHttpHandler/releases/tag/redist-20230728-01

上記ページから下記2つのunitypackageをDLして、プロジェクトにインストールする.

- Cysharp.Net.Http.YetAnotherHttpHandler.Dependencies.unitypackage
- Grpc.Net.Client.Dependencies.unitypackage

`Packages/manifest.json` にYetAnotherHttpHandlerの依存を追加する.

```json:Packages/manifest.json
    "com.cysharp.yetanotherhttphandler": "https://github.com/Cysharp/YetAnotherHttpHandler.git?path=src/YetAnotherHttpHandler",
```

また、生成後のファイルでGoogle.Protobufが必要となるのでこれのdllも追加しておく.

```shell-session
$ curl -Lo protobuf.zip https://www.nuget.org/api/v2/package/Google.Protobuf/3.24.3
$ unzip protobuf.zip -d protobuf
$ mkdir -p Assets/Plugins/Google.Protobuf
$ cp protobuf/lib/net45/Google.Protobuf.dll Assets/Plugins/Google.Protobuf/
```


## 2-2. protoファイルからC＃コードを生成する

nugetのGrpc.Toolsから必要なコマンドを抜き出す.

- https://www.nuget.org/packages/Grpc.Tools/

```shell-session
$ curl -Lo grpctools.zip https://www.nuget.org/api/v2/package/Grpc.Tools/2.58.0
$ unzip grpctools.zip -d grpctools
```

梱包されている下記2つの実行バイナリを利用する.

```plaintext
grpctools/tools/macosx_x64/grpc_csharp_plugin
grpctools/tools/macosx_x64/protoc
```

実行バイナリに実行権限をつけておく.

```shell-session
$ chmod +x grpctools/tools/macosx_x64/protoc
$ chmod +x grpctools/tools/macosx_x64/grpc_csharp_plugin
```

serverで利用しているprotoをDL

```shell-session
$ curl -L -O https://raw.githubusercontent.com/mattak/grpc-go/cloudrun/examples/helloworld/helloworld/helloworld.proto
```

出力先を作っておく

```shell-session
$ mkdir -p Assets/Scripts/Protos
```

protocを使ってコード生成する

```shell-session
PROTOC=$PWD/grpctools/tools/macosx_x64/protoc
GRPC_PLUGIN=$PWD/grpctools/tools/macosx_x64/grpc_csharp_plugin

$PROTOC -I ./ \
  --csharp_out=./Assets/Scripts/Protos \
  --grpc_out=./Assets/Scripts/Protos \
  --plugin=protoc-gen-grpc=$GRPC_PLUGIN \
  ./helloworld.proto
```

下記ファイルが生成されていればOK

```shell-session
$ find Assets/Scripts/Protos -type f
Assets/Scripts/Protos/Helloworld.cs
Assets/Scripts/Protos/HelloworldGrpc.cs
```

## 2-3. Unity上からリクエストを呼び出してみる

シンプルなスクリプトを記述して、適当にGameObjectにつける.

```csharp
using Cysharp.Net.Http;
using Cysharp.Threading.Tasks;
using Grpc.Net.Client;
using Helloworld;
using UnityEngine;

namespace GrpcSample
{
    public class SampleRequestRunner : MonoBehaviour
    {
        private const string URL = "https://FIXME.a.run.app"; // FIXME: 先ほどメモったCloudRunのURLを入力する

        private void Start()
        {
            Request();
        }

        private void Request()
        {
            using var handler = new YetAnotherHttpHandler();
            using var channel = GrpcChannel.ForAddress(URL, new GrpcChannelOptions
            {
                HttpHandler = handler,
                DisposeHttpClient = true,
            });
            var client = new Greeter.GreeterClient(channel);

            var reply = client.SayHello(new HelloRequest {Name = "UnityClient"});
            Debug.Log("Greeting: " + reply.Message);
        }
    }
}
```

ログに下記のようにメッセージが表示されていれば、疎通成功!

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15000/6ce01f3c-4fb5-21c7-842f-32e29ee4c779.png" width=640 />


表示されない場合は一度cloud runをデプロイし直して、サーバが立ち上がっていることを確認してから再度リクエストするとうまくいく.


# 感想

一昔前だと、macではC# + gRPC環境構築するのが難しかったりしたが、けっこう楽ちんに構築できた!
YetAnotherHttpHandlerをOSSで公開してくれたCysharpさんに圧倒的感謝!