はじめに
WCF では OperationContext の IncomingMessageHeaders と OutgoingMessageHeaders を使って、 カスタムヘッダーをクライアントから送ったり、サーバーから返したりできた。
gRPC には Metadata という同じような機能があるので、 WCF から gRPC に移行する際にカスタムヘッダーは Metadata を使うことになる。
そこで、WCF から gRPC への移行に protobuf-net.Grpc を使う場合、 Metadata をどう扱えるのか試してみた。
Shared プロジェクト作成
クライアントとサーバーの両方で使う、サービスコントラクトとデータコントラクトを定義する。 プロジェクトの種類は .NET Core クラスライブラリ。 ServiceContractAttribute を使うために System.ServiceModel.Primitives、 CallContext を使うために protobuf-net.Grpc のパッケージを追加しておく。
using System.Runtime.Serialization; using System.ServiceModel; using System.Threading.Tasks; using ProtoBuf.Grpc; namespace CodeFirstGrpc.Shared { [ServiceContract] public interface IGreetingService { ValueTask<HelloReply> HelloAsync( HelloRequest request, CallContext context = default); } [DataContract] public class HelloRequest { [DataMember(Order = 1)] public string Name { get; set; } } [DataContract] public class HelloReply { // クライアントから Metadata で送られてきた値を格納して返すためのもの [DataMember(Order = 1)] public string ClientId { get; set; } [DataMember(Order = 2)] public string Message { get; set; } } }
Server プロジェクト作成
gRPC サービスプロジェクトを作成し、コードファーストで gRPC サービスを実装する。 コードファーストでやるために、 protobuf-net.Grpc.AspNetCore パッケージを参照しておく。
using System; using System.Linq; using System.Threading.Tasks; using CodeFirstGrpc.Shared; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Grpc.Core; using ProtoBuf.Grpc; using ProtoBuf.Grpc.Server; namespace CodeFirstGrpc.Server { 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>(); }); } public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddCodeFirstGrpc(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<GreetingService>(); }); } } public class GreetingService : IGreetingService { public async ValueTask<HelloReply> HelloAsync( HelloRequest request, CallContext context = default) { await context.ServerCallContext.WriteResponseHeadersAsync(new Metadata { new Metadata.Entry("X-ServerId", Guid.NewGuid().ToString()), }); return new HelloReply { ClientId = GetClient(context), Message = $"Hello, {request.Name}.", }; } // クライアントから送られてきたカスタムヘッダーから値を取り出す。 // ヘッダーのキーはすべて小文字に変わっているので注意。 static string GetClient(CallContext context) => context.RequestHeaders .Where(x => x.Key == "x-clientid") .Select(x => x.Value) .FirstOrDefault(); } }
クライアントから送られてくる予定のカスタムヘッダーは、CallContext から取り出せる。 サーバーがカスタムヘッダーを返す場合は、CallContext が持つ ServerCallContext を使って書き込む。
Client プロジェクト作成
.NET Core コンソールアプリのプロジェクトを作成し、 gRPC サービスを呼び出すためのクライアントを実装する。 コードファーストで実装した gRPC サービスを呼び出すために、 Grpc.Net.Client と protobuf-net.Grpc のパッケージを参照しておく。
using System; using System.Linq; using System.Threading.Tasks; using CodeFirstGrpc.Shared; using Grpc.Core; using Grpc.Net.Client; using ProtoBuf.Grpc; using ProtoBuf.Grpc.Client; namespace CodeFirstGrpc.Client { class Program { static async Task Main(string[] args) { var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = channel.CreateGrpcService<IGreetingService>(); // gRPC サーバーに送信するカスタムヘッダー var callOptions = new CallOptions(headers: new Metadata { new Metadata.Entry("X-ClientId", Guid.NewGuid().ToString()), }); // サーバーが返すヘッダーを参照したい場合は // CaptureMetadata を指定する var callContext = new CallContext( callOptions: callOptions, flags: CallContextFlags.CaptureMetadata); var reply = await client.HelloAsync( new HelloRequest { Name = "Kubo" }, callContext); Console.WriteLine($"Message: {reply.Message}"); Console.WriteLine($"ClientId: {reply.ClientId}"); // サーバーから返ってくるヘッダーのキーも小文字になっている var serverId = callContext.ResponseHeaders() .Where(x => x.Key == "x-serverid") .Select(x => x.Value) .FirstOrDefault(); Console.WriteLine($"ServerId: {serverId}"); Console.ReadLine(); } } }
クライアントからサーバーに送るカスタムヘッダーは、CallOption に Metadata で渡す。 サーバーから返ってくるカスタムヘッダーは CallContext から取り出せるが、 そのためには CallContext 作成時に CallContextFlags.CaptureMetadata を指定しておく必要がある。
実行結果
クライアントから送ったカスタムヘッダーーと、 サーバーから返ってきたカスタムヘッダー、 両方がちゃんと表示された。
おわりに
コードファースト ASP.NET Core gRPC でも、Metadata を使ってカスタムヘッダーをクライアントから送ったり、 サーバーから返したりできることを確認。 WCF から gRPC に移行するための課題がまた 1 つ解消された。 次は認証を試したい。