ASP.NET Core で gRPC と gRPC-Web の両方に対応できるか試した

はじめに

Grpc.AspNetCore.Web を使えば、プロキシを別に立てる必要なく、ASP.NET Core のパイプラインに gRPC-Web を組み込める。

gRPC-Web を組み込んだ場合に、gRPC-Web だけではなく通常の gRPC も使えるのか、気になったので試してみた。

gRPC サーバー

ASP.NET Core gRPC サービスのプロジェクトを新規作成。ターゲットフレームワーク .NET 5 で。

gRPC-Web をサポートするため、gRPC サーバー側に、Grpc.AspNetCore.Web をインストールする。

www.nuget.org

greet.proto

syntax = "proto3";

option csharp_namespace = "HelloGrpcWeb";

package greet;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string firstName = 1;
  string lastName = 2;
}

message HelloReply {
  string message = 1;
}

GreeterService

using System.Threading.Tasks;
using Grpc.Core;

namespace HelloGrpcWeb
{
    public class GreeterService : Greeter.GreeterBase
    {
        public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
        {
            return Task.FromResult(new HelloReply
            {
                Message = $"Hello {request.FirstName} {request.LastName}",
            });
        }
    }
}

Startup

ASP.NET Core のパイプラインに gRPC-Web を組み込む。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace HelloGrpcWeb
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc();
        }

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

            app.UseRouting();

            // ASP.NET Core のパイプラインに gRPC-Web を組み込む
            app.UseGrpcWeb();

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

                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");
                });
            });
        }
    }
}

Program

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace HelloGrpcWeb
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

gRPC クライアント

コンソールアプリケーションのプロジェクトを新規作成する。ターゲットフレームワークはこちらも .NET 5。

[接続済みサービスの追加] - [サービス参照] - [gRPC] で、greeter.proto を選択すると、必要なパッケージがインストールされ、クライアントのコードが生成される。gRPC-Web を呼び出すために、Grpc.Net.Client.Web もインストールしておく。

www.nuget.org

通常の gRPC と gRPC-Web をそれぞれ呼び出すサンプルは次の通り。

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;

namespace HelloGrpcWeb.Client
{
    class Program
    {
        const string Address = "https://localhost:5001";

        static async Task Main(string[] args)
        {
            await Grpc();
            await GrpcWeb();

            Console.ReadLine();
        }

        static async Task Grpc()
        {
            var channel = GrpcChannel.ForAddress(Address);

            var client = new Greeter.GreeterClient(channel);

            var reply = await client.SayHelloAsync(new HelloRequest
            {
                FirstName = "Takefusa",
                LastName = "Kubo",
            });

            Console.WriteLine($"gRPC:{reply.Message}");
        }

        static async Task GrpcWeb()
        {
            var channel = GrpcChannel.ForAddress(Address, new GrpcChannelOptions
            {
                HttpHandler = new GrpcWebHandler(
                    new HttpClientHandler()),
            });

            var client = new Greeter.GreeterClient(channel);

            var reply = await client.SayHelloAsync(new HelloRequest
            {
                FirstName = "Takehiro",
                LastName = "Tomiyasu",
            });

            Console.WriteLine($"gRPC Web:{reply.Message}");
        }
    }
}

実行結果

上のウィンドウに、gRPC と gRPC-Web がそれぞれ呼び出されているログが確認できた。

f:id:griefworker:20210527094848p:plain

おわりに

別にプロキシを立てることなく、1つの ASP.NET Core アプリで gRPC と gRPC-Web の両方に対応できるのは素晴らしい。Microsoft の本気を感じた。