gRPC の通信する .NET Core のプログラムを動かして、HTTP2 の通信の中身まで見てみた

###背景
Windows 上でのプロセス間通信といえば、WinSock、.NET Remoting、XML Web Service、WCF といろいろあるが、新しいものはできることが多くて複雑な印象がある。
これらとは gRPC の用途が完全に一致しないので単純に比較できるものではないと思うが、そもそも gRPC を使ったことがないのでとりあえず試しに使ってみる。

というかそろそろ流行りに乗っておこうと思っただけ。

###参考
~~わかりにくさに~~定評のある Docs の通りに進める。(やってみると案の定うまく進まないところがあったので、つまずいた箇所の対処方法なども本記事にメモした)
[チュートリアル: ASP.NET Core で gRPC のクライアントとサーバーを作成する](
https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0&tabs=visual-studio-code)

###検証環境
- Windows 10
- Visual Studio Code
- .NET Core 3.0 SDK
- Wireshark (通信を見る用)

##作ってみる
####1. サービスの作成
任意のフォルダーを VS Code を開いて、ターミナルから以下のコマンドを実行する。

```PowerShell
dotnet new grpc -o GrpcGreeter # サービスのサンプルを作成
code -r GrpcGreeter # できたサンプルをGrpcGreeterフォルダーに移動
```

コマンドを実行後、以下のようなファイルが作成されるが、必要なパッケージもついでにいろいろとダウンロードをしてくれている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/608dda03-9d35-417f-7900-c1202c6da5c7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/b9b6627d-b28e-c47f-6918-dd45d5c6a098.png)

これもYes
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/dc792308-9c3a-0a6f-2b25-70f4b6ed4b40.png)

デバッグ実行するとブラウザでページが開いた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/667b1e1b-858f-3a5e-f3b0-adca8bcf41e4.png)

ふーん
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/809f28c4-4ff9-31dd-fdbf-54f8f1a9cdd4.png)


ソースを見てみると、たしかに GET でリクエストを受けるたときにテキストでメッセージを表示するようになっている。

```Startup.cs
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<GreeterService>();

                endpoints.MapGet("/", async context =>
                {
                   // ここ
                    await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                });
            });
        }
```

####2. クライアントの作成
VS Code 上のターミナルで以下のコマンドを実行する。

```PowerShell
cd .. # サービスのひとつ上のフォルダーに移動して
dotnet new console -o GrpcGreeterClient # クライアントを作成
code -r GrpcGreeterClient # フォルダーを作ってファイルを移動
```

これも Yes 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/dd90df27-fe00-0c4d-c11a-245451234c29.png)

以下のコマンドでパッケージを追加する実行する。Grpcって書いてあるのでなんかそれっぽい。

```PowerShell
dotnet add GrpcGreeterClient.csproj package Grpc.Net.Client
dotnet add GrpcGreeterClient.csproj package Google.Protobuf
dotnet add GrpcGreeterClient.csproj package Grpc.Tools
```

サービスのプロジェクトから Protos フォルダーごとクライアントのフォルダーにコピーする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/f2a0c0fc-6a84-e481-ba39-fb96c999d40f.png)


.proto ファイルというのは Wikipedia によるとデータ構造とサービスを定義するものらしい。
>Protocol Buffers（プロトコルバッファー）はインタフェース定義言語 (IDL) で構造を定義する通信や永続化での利用を目的としたシリアライズフォーマットであり、Googleにより開発されている。
...
>データ構造およびサービスはプロトコル定義ファイル (Proto Definition file (.proto)) でデザインされ、protocプログラムによりコンパイルされる。このコンパイル作業により目的のサービスに適合するコードが生成される。

(参考) [Protocol Buffers](https://ja.wikipedia.org/wiki/Protocol_Buffers)


最初に作ったサービス側のフォルダーに確かにそんなやつがいた。
ふーん、サービスを定義しているらしい。最後に通信を見るときの参考になりそう。

```greet.proto
syntax = "proto3";

option csharp_namespace = "GrpcGreeter";

package Greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}
```



GrpcGreeterClient.csproj に \<Protobuf> 要素を追加して、こんな感じにする。

```GrpcGreeterClient.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.10.1" />
    <PackageReference Include="Grpc.Net.Client" Version="2.25.0" />
    <PackageReference Include="Grpc.Tools" Version="2.25.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
  </ItemGroup>

</Project>
```

ビルドして GrpcGreeter.dll を予め作っておく必要があるらしい。

```PowerShell
dotnet build
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/65651774-1327-8add-ea71-c975c74f3ee4.png)


Program.cs を書き換えてクライアントを作成する。
通信先を決めてクライアントのインスタンスを作成、その後リクエストを送って受信って感じに見える。
メモがてらコメントを追記

```Program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
using GrpcGreeter;
using Grpc.Net.Client;

namespace GrpcGreeterClient
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // The port number(5001) must match the port of the gRPC server.
            // 通信先を決める
            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            // クライアントを生成
            var client =  new Greeter.GreeterClient(channel);
            // リクエスト作成、呼び出し、レスポンスを入れる
            var reply = await client.SayHelloAsync(
                              new HelloRequest { Name = "GreeterClient" });
            Console.WriteLine("Greeting: " + reply.Message);
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}
```


####3. 動かしてみる
サービスをデバッグ実行して、ブラウザが起動することを確認できた。
クライアントもデバッグ実行して例外が発生することを確認できた。(そんな手順は Docs にはない...)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/a4c15eb4-0378-a4bc-e830-7636a410c4e0.png)

"Error starting gRPC call: The SSL connection could not be established, see inner exception." というメッセージより、SSL 周りで問題があるように見える。内部例外を見ろということだけど VS Code でそのまま内部例外を見れるのかよくわからなかった。内部例外を表示するコードを書こうかとも思ったが、サービスを起動したときの証明書の警告が怪しすぎるのでそれをなんとかすればいけるのではと思った。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/394877a1-827b-e70c-fdf2-dc00f50bb715.png)

一応、ググってみると、それっぽいのが見つかった。やっぱり、証明書かなあ。

>.NET gRPC クライアントでは、サービスに信頼された証明書が必要です。 信頼された証明書を使用せずに gRPC サービスを呼び出すと、次のエラーメッセージが返されます。
>>ハンドルされていない例外です。 System.net.http.httprequestexception: SSL 接続を確立できませんでした。内部例外を参照してください。 ---> を実行しています。 AuthenticationException: 検証プロシージャによると、リモート証明書が無効です。

>このエラーは、アプリをローカルでテストしていて、ASP.NET Core HTTPS 開発証明書が信頼されていない場合に表示されることがあります。 この問題を解決する手順については、「Windows と macOS で ASP.NET Core HTTPS 開発証明書を信頼します」を参照してください。

(参考) [信頼されていない/無効な証明書を使用して gRPC サービスを呼び出す](https://docs.microsoft.com/ja-jp/aspnet/core/grpc/troubleshoot?view=aspnetcore-3.0#call-a-grpc-service-with-an-untrustedinvalid-certificate)

サービス側を停止して、このコマンドを実行して信頼しておく。

```PowerShell
dotnet dev-certs https --trust
```

[Windows および macOS で ASP.NET Core HTTPS 開発証明書を信頼する](https://docs.microsoft.com/ja-jp/aspnet/core/security/enforcing-ssl?view=aspnetcore-3.0&tabs=visual-studio#trust-the-aspnet-core-https-development-certificate-on-windows-and-macos)

再度サービス側をデバッグ実行すると、今度はブラウザの警告が消えた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/20312d74-27ff-e264-00a7-32781b4fa56f.png)


クライアントもデバッグ実行するとまた例外が...と思ったけど、これはキー入力待ちのコードなので、そもそもデバッグ実行するのが良くなかった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/420e8324-a602-7ef2-5fd0-0eb03aaef4bd.png)

クライアントのフォルダーでビルドして生成された .exe を実行する。

```PowerShell
dotnet build
.\bin\Debug\netcoreapp3.0\GrpcGreeterClient.exe
```

今度は "Greeting: Hello GreeterClient" というメッセージが表示された。(キー入力待ちで停止)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/0990e479-a73f-7a21-b822-33426becf6d9.png)

サービス側の DEBUG CONSOLE にも 200 OK で応答しているログが記録されていた。
Content-type は application/grpc となるらしい。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/ddf8e359-8b20-3764-fae3-f65e16d12a9c.png)


####3. 通信を見てみる
Wireshark では最近の新しめの暗号化スイートの TLS 通信はサーバー証明書の秘密鍵を使っても復号化できないので、サービス - クライアントをHTTP で通信するよう変更する。
Docs には書いていないけど名前空間の追加が必要だった。

```Startup.cs
using Microsoft.AspNetCore.Server.Kestrel.Core;
// 略

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.ConfigureKestrel(options =>
                    {
                        // Setup a HTTP/2 endpoint without TLS.
                        options.ListenLocalhost(5000, o => o.Protocols = 
                            HttpProtocols.Http2);
                    });                    
                    webBuilder.UseStartup<Startup>();
                });
```

(参考) [MacOS で gRPC アプリを開始できません ASP.NET Core](https://docs.microsoft.com/ja-jp/aspnet/core/grpc/troubleshoot?view=aspnetcore-3.0#unable-to-start-aspnet-core-grpc-app-on-macos)


クライアントも HTTP でサービスを呼び出すようにコードを変更する。


```Program.cs
        static async Task Main(string[] args)
        {
            // This switch must be set before creating the GrpcChannel/HttpClient.
            AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

            // The port number(5001) must match the port of the gRPC server.
            var channel = GrpcChannel.ForAddress("http://localhost:5000");
            var client =  new Greeter.GreeterClient(channel);
            var reply = await client.SayHelloAsync(
                              new HelloRequest { Name = "GreeterClient" });
            Console.WriteLine("Greeting: " + reply.Message);
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
```


(参考) [.NET Core クライアントを使用してセキュリティで保護される gRPC サービスを呼び出す](https://docs.microsoft.com/ja-jp/aspnet/core/grpc/troubleshoot?view=aspnetcore-3.0#call-insecure-grpc-services-with-net-core-client)

そして、サービスをデバッグ実行して、クライアントをビルド→起動する。

```PowerShell
dotnet build
.\bin\Debug\netcoreapp3.0\GrpcGreeterClient.exe
```

Wireshark を起動してパケットを取った。が、HTTP が見えない。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/29cd0478-e652-12f5-1dd1-72afb577c8c1.png)

Wireshark wiki の HTTP2 のサンプルも同様だったけど、「need to use Decode as HTTP2」すれば見えるようになった。

5000 番ポートの通信を右クリック -> ...としてデコード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/69d356ad-71b2-5a0a-067d-a89ef431e0b5.png)

HTTP2 にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/ee0d4487-fa53-b23c-68bf-03c67039e3c4.png)


(参考) https://wiki.wireshark.org/HTTP2


見えた！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/d1b66a83-ce7a-7cd2-d29d-c4f189b77464.png)

#####リクエスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/2e4f650e-dc9e-a2ab-8762-5f8e8e0e12bb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/b48e5b15-8a13-4876-b50b-c396c84e0e34.png)

##### レスポンス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/f6c6c367-6e42-5a12-d3dd-157fe22bed13.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/096a579f-681d-2b93-c78d-fe9eb2cfc219.png)

レスポンスがわかりにくいのは HTTP2 通信だからしかたないのか、gRPC はそういうものなのか、理解が足りない。。