2020-05-25

DotNet Core 中使用 gRPC

DotNet Core 中使用 gRPC


gRPC 是一个由 Google 开源的,跨语言的,高性能的远程过程调用(RPC)框架。 gRPC 使客户端和服务端应用程序可以透明地进行通信,并简化了连接系统的构建。它使用 HTTP/2 作为通信协议,使用 Protocol Buffers 作为序列化协议

gRPC 简介

gRPC(gRPC Remote Procedure Calls)是一个由 Google 开源的,跨语言的,高性能的远程过程调用(RPC)框架。 gRPC 使客户端和服务端应用程序可以透明地进行通信,并简化了连接系统的构建。它使用 HTTP/2 作为通信协议,使用 Protocol Buffers 作为序列化协议。

官网:https://grpc.io/

Github:https://github.com/grpc/grpc

DotNet Core 官方示例:https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/grpc

gRPC 的主要优点
  • 现代高性能轻量级 RPC 框架。
  • 约定优先的 API 开发,默认使用 Protocol Buffers 作为描述语言,允许与语言无关的实现。
  • 可用于多种语言的工具,以生成强类型的服务器和客户端。
  • 支持双向流式的请求和响应,对批量处理、低延时场景友好。
  • 通过 Protocol Buffers 二进制序列化减少网络使用。
  • 使用 HTTP/2 进行传输
gRPC 适用的场景
  • 高性能的轻量级微服务。
  • 多语言混合开发的 Polyglot 系统。
  • 需要处理流式处理请求或响应的点对点实时通信服务。
gRPC 不适用的场景
  • 浏览器可访问的 API:浏览器不完全支持 gRPC。虽然 gRPC-Web 可以提供浏览器支持,但是它有局限性,引入了服务器代理。
  • 广播实时通信:gRPC 支持通过流进行实时通信,但不存在向已注册连接广播消息的概念。
  • 进程间通信:进程必须承载 HTTP/2 才能接受传入的 gRPC 调用,对于 Windows,进程间通信管道是一种更快速的方法。
gRPC 支持的语言

目前 gRPC 已经实现了对主流语言的支持,以下语言在 gRPC 的 Github 中都提供了实现。

支持的语言

在 DotNet Core 中使用 gRPC

创建服务端

Visual Studio 2019 中已经集成了 gRPC 项目的模版,我们可以通过这个模版快速的创建一个基于 DotNet Core 的 gRPC 项目。

创建好的项目结构如下:

这时候项目不用做任何修改就可以运行了,那么这个项目和普通的 DotNet Core 项目有什么不同呢?
首先项目文件 GrpcService.csproj 中引入了 Grpc.AspNetCore 包。

  <ItemGroup>    <PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />  </ItemGroup>

appsettings.json 文件中多出了一个 Kestrel 节点,配置 Protocols 使用 Http2 协议。

 "Kestrel": { "EndpointDefaults": {  "Protocols": "Http2" } }

服务端 GreeterService 类的实现如下:

 public class GreeterService : Greeter.GreeterBase {  private readonly ILogger<GreeterService> _logger;  public GreeterService(ILogger<GreeterService> logger)  {   _logger = logger;  }  public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)  {   return Task.FromResult(new HelloReply   {    Message = "Hello " + request.Name   });  } }

服务端 Startup 类中注入了 gRPC 服务:

 public void ConfigureServices(IServiceCollection services) {  services.AddGrpc(); } 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");   });  }); }

gRPC 工具会根据 proto 文件自动生成需要使用的类,生成的类会存放在项目的 obj\Debug\netcoreapp3.1 目录下:

创建客户端

客户端项目需要手动的创建,创建方法也很简单,直接在解决方案中添加一个新的项目即可,这里我创建了一个空的 Web 项目。

项目创建好了以后首先要把 proto 文件添加到项目中,这里需要用到 dotnet-grpc 这个工具。
在命令行下安装 gRPC 工具:

dotnet tool install dotnet-grpc -g

安装完以后从命令行进入 GrpcClient 项目的目录后,添加服务端的 proto 文件到客户端。

 dotnet grpc add-file ..\GrpcService\Protos\greet.proto 

也可以使用远程路径的 proto 文件:

dotnet grpc add-url https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/keyvaluestore.proto -o /Protos/keyvaluesrore.proto

导入 proto 文件以后,GrpcClient 项目文件中会增加如下代码:

  <ItemGroup>    <Protobuf Include="..\GrpcService\Protos\greet.proto" Link="Protos\greet.proto" />    <Protobuf Include="XXXX/Protos/keyvaluesrore.proto" Link="Protos\keyvaluesrore.proto"><SourceUrl>https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/keyvaluestore.proto</SourceUrl>    </Protobuf>  </ItemGroup>

dotnet-grpc 除了以上用法还支持以下命令,详细用法可以查阅 Microsoft Docs。

  • dotnet grpc add-file
  • dotnet grpc add-url
  • dotnet grpc remove
  • dotnet grpc refresh

接下来修改 Startup 中的代码,在 ConfigureServices(IServiceCollection services) 方法注入 gRPC 客户端代码:

services.AddGrpcClient<GreeterClient>(options => options.Address = new Uri("https://localhost:5001"));

注:Uri("https://localhost:5001") 中使用服务端的端口和地址。

Configure 方法中添加响应代码:

 app.UseEndpoints(endpoints => {  endpoints.MapGet("/", async context =>  {   GreeterClient client = context.RequestServices.GetService<GreeterClient>();   HelloRequest request = new HelloRequest();   request.Name = "Charles";   var reply = await client.SayHelloAsync(request);   await context.Response.WriteAsync(reply.Message);  }); });

如果不想使用注入的方式也可以直接调用:

 var httpClientHandler = new HttpClientHandler(); var httpClient = new HttpClient(httpClientHandler); var channel = GrpcChannel.ForAddress(Address); var client = new GreeterClient(channel); HelloRequest request = new HelloRequest {  Name = "Charles" }; var reply = await client.SayHelloAsync(request); await context.Response.WriteAsync(reply.Message);

启动项目后在客户端可以看到输出了服务端返回的内容:

Hello Charles
限制消息大小

消息大小限制是一种有助于防止 gRPC 消耗过多资源的机制。gRPC 使用每个消息的大小限制来管理传入和传出消息。 默认情况下,gRPC 将传入消息限制为 4 MB。 传出消息没有限制。
在服务端上,可以使用 AddGrpc 为应用中的所有服务配置 gRPC 消息限制:

 services.AddGrpc(options => {  options.MaxReceiveMessageSize = 1 * 1024 * 1024; // 1 MB  options.MaxSendMessageSize = 1 * 1024 * 1024; // 1 MB });
调用不安全的 gRPC 服务:

若要使客户端调用不安全的 gRPC 服务,需要修改客户端的配置。

 AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
调用不受信任、无效证书调用 gRPC 服务

DotNet gRPC 客户端要求服务具有受信任的证书,若要调用不受信任、无效证书调用 gRPC 服务,需要修改客户端请求的代码:

 services.AddGrpcClient<GreeterClient>(options => options.Address = new Uri(Address)). ConfigurePrimaryHttpMessageHandler(provider => {  var handler = new SocketsHttpHandler();  handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; // 允许不受信任、无效证书  return handler; });

非注入方式:

 var httpClientHandler = new HttpClientHandler {  ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; var httpClient = new HttpClient(httpClientHandler); var channel = GrpcChannel.ForAddress(Address); var client = new GreeterClient(channel); HelloRequest request = new HelloRequest {  Name = "Charles" }; var reply = await client.SayHelloAsync(request);

身份验证和授权

服务端

为服务端加入身份验证也很简单,首先需要为项目引入 Microsoft.AspNetCore.Authentication.JwtBearer 这个包。

 <ItemGroup>  <PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />  <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" /> </ItemGroup>

修改服务端 Startup 类的代码,分别在 ConfigureConfigureServices 方法中加入如下代码:

 services.AddAuthorization(options => {  options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>  {   policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);   policy.RequireClaim(ClaimTypes.Name);  }); }); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)  .AddJwtBearer(options =>  {   options.TokenValidationParameters =    new TokenValidationParameters    {     ValidateAudience = false,     ValidateIssuer = false,     ValidateActor = false,     ValidateLifetime = true,     IssuerSigningKey = SecurityKey    };  });
 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => {  endpoints.MapGrpcService<GreeterService>();  endpoints.MapGet("/getToken", context =>  {   return context.Response.WriteAsync(GenerateJwtToken(context.Request.Query["name"]));  }); });

生成 Token 的方法:

 private string GenerateJwtToken(string name) {  if (string.IsNullOrEmpty(name))  {   throw new InvalidOperationException("Name is not specified.");  }  var claims = new[] { new Claim(ClaimTypes.Name, name) };  var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);  var token = new JwtSecurityToken("JwtSecurityIssuer", "JwtSecurityClients", claims, expires: DateTime.Now.AddSeconds(60), signingCredentials: credentials);  return JwtTokenHandler.WriteToken(token); }
客户端

客户端的实现逻辑是在请求服务端之前先从服务端获取到 Token,在之后调用响应服务的时候将 Token 放入请求头中传入服务端,如果不传入直接请求服务端会返回 401。

 var httpClient = new HttpClient(); var httpRequest = new HttpRequestMessage {  RequestUri = new Uri($"{Address}/getToken?name=Charles"),  Method = HttpMethod.Get,  Version = new Version(2, 0) }; var tokenResponse = await httpClient.SendAsync(httpRequest); tokenResponse.EnsureSuccessStatusCode(); var token = await tokenResponse.Content.ReadAsStringAsync(); Metadata headers = null; if (token != null) {  headers = new Metadata  {   { "Authorization", $"Bearer {token}" }  }; } var channel = GrpcChannel.ForAddress(Address); var client = new GreeterClient(channel); HelloRequest request = new HelloRequest {  Name = "Charles" }; var reply = await client.SayHelloAsync(request, headers);

总结

以上就是在 DotNet Core 中使用 gRPC 的常用方法了,想深入学习可以去查看 gRPC DotNet 项目的 Github,里面有很多实例可以参考:https://github.com/grpc/grpc-dotnet/tree/master/examples


No comments:

Post a Comment