gRPC のデバッグ用クライアント BloomRPC を使ってみた


[【Qiita】簡単にgRPCが叩けるOSSツール BloomRPC](https://qiita.com/Tanashun/items/1cea68ff80929bfa997e) で紹介されている `BloomRPC` を使ってみました。サービス開発時のデバッグに便利そうです。

# BloomRPC とは

[【Github】BloorRPC](https://github.com/uw-labs/bloomrpc/blob/master/README.md)

gRPC の GUI クライアントです。
JSON 形式で記述したリクエストデータを実行中のサービスに送信し、レスポンスデータの内容を確認することができます。
ストリームにも対応しています。

# スクリーンショット

![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280969/10b1b5a5-f02e-12b7-1188-ac794483f04c.png)

# インストール

各プラットフォーム向けのインストーラーが公開されています。
私は windows 版（bloomrpc-setup-1.3.1.exe）をインストールしました。

# 使い方

1. Protos の右上の＋ボタンをクリックし、対象の proto ファイルを読み込みます。
2. 実行したいメソッドをツリーから選択します。
3. サービスのエンドポイントを入力します。
4. リクエスト欄のエディタにリクエストデータを記述します。
5. リクエスト欄とレスポンス欄の間にある実行ボタンをクリックします。
6. レスポンス欄にレスポンスデータが表示されます。

## ストリーム操作

Interactive / manual によってストリーム送受信の操作が変わります。

#### リクエストストリームの送信

Interactive

1. 実行ボタンを押すとストリーム送信が開始され、エディタに記述されたリクエストが送信されます。
2. リクエスト側にタブが追加され、送信したリクエストの内容が表示されます。
3. Push ボタンを押すたびにエディタに記述されているリクエストが送信されます。
4. Commit ボタンを押すとストリーム送信が完了します。

manual

* Push / Commit ボタンは表示されなくなり、一回きりの送信になります。リクエスト側のエディタにどのように記述するのかわかりませんでした。リクエストを配列で記述するとレスポンスには空の配列が返され、期待した結果ではありませんでした。

#### レスポンスストリームの受信

Interactive

* サービスからレスポンスを受信するたびにタブが追加され、レスポンスの内容が表示されます。

manual

* サービスからレスポンスを受信するたびに上書き表示されます。タブは追加されません。

### リクエストヘッダー

METADATA 欄でリクエストヘッダーに格納するデータを指定することができます。
下の画像は Visual Studio でデバッグ実行したときのリクエストヘッダーの内容です。METADATA 欄に記述した "id":"123" が格納されていることがわかります。

![header.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280969/dab6266c-bde7-f57c-01ba-edf38840ab27.png)



## 確認に使用した proto ファイル

```proto

syntax = "proto3";

import "timestamp.proto";

option csharp_namespace = "SampleIDL";

message SampleRequest
{
	string Name = 1;
	int32 Count = 2;
}

message SampleResponse
{
	string Name = 1;
	google.protobuf.Timestamp UpdateTime = 2;
}

message SampleResponseList
{
	repeated SampleResponse Items = 1;
}

service SampleService
{
	rpc Unary(SampleRequest) returns (SampleResponseList);
	rpc ClientStream(stream SampleRequest) returns (SampleResponseList);
	rpc ServerStream(SampleRequest) returns (stream SampleResponse);
	rpc DuplexStream(stream SampleRequest) returns (stream SampleResponse);
}
```

## 確認に使用したサービスの実装

SampleRequest.Count で指定された数だけ SampleResponse を生成して返しているだけです。

```c#
class SampleServiceImpl : SampleService.SampleServiceBase
{
    internal SampleServiceImpl() : base()
    {
    }

    public async override Task<SampleResponseList> Unary(SampleRequest request, ServerCallContext context)
    {
        SampleResponseList list = new SampleResponseList();

        foreach (SampleResponse response in GetResponses(request))
        {
            await RamdomDelayAsync();
            list.Items.Add(response);
        }

        return list;
    }

    public async override Task<SampleResponseList> ClientStream(IAsyncStreamReader<SampleRequest> requestStream, ServerCallContext context)
    {
        SampleResponseList list = new SampleResponseList();

        while (await requestStream.MoveNext())
        {
            foreach (SampleResponse response in GetResponses(requestStream.Current))
            {
                await RamdomDelayAsync();
                list.Items.Add(response);
            }
        }

        return list;
    }

    public async override Task ServerStream(SampleRequest request, IServerStreamWriter<SampleResponse> responseStream, ServerCallContext context)
    {
        foreach (SampleResponse response in GetResponses(request))
        {
            await RamdomDelayAsync();
            await responseStream.WriteAsync(response);
        }
    }

    public async override Task DuplexStream(IAsyncStreamReader<SampleRequest> requestStream, IServerStreamWriter<SampleResponse> responseStream, ServerCallContext context)
    {
        while (await requestStream.MoveNext())
        {
            foreach (SampleResponse response in GetResponses(requestStream.Current))
            {
                await RamdomDelayAsync();
                await responseStream.WriteAsync(response);
            }
        }
    }

    private IEnumerable<SampleResponse> GetResponses(SampleRequest request)
    {
        for (int i = 0; i < request.Count; ++i)
        {
            yield return new SampleResponse()
            {
                Name = string.Format("{0} {1}", request.Name, i + 1),
                UpdateTime = Timestamp.FromDateTimeOffset(DateTimeOffset.Now)
            };
        }
    }

    private Task RamdomDelayAsync()
    {
        return Task.Delay(random.Next(1000));
    }
    static Random random = new Random();

}
```