ASP.NET Core 中的 gRPC JSON 转码

作者:James Newton-King

gRPC 是一种高性能远程过程调用 (RPC) 框架。 gRPC 使用 HTTP/2、流式传输、Protobuf 和消息协定来创建高性能的实时服务。

gRPC 有一个限制,即不是所有平台都可以使用它。 浏览器并不完全支持 HTTP/2,这使得 REST API 和 JSON 成为将数据引入浏览器应用的主要方式。 尽管 gRPC 带来了很多好处,REST API 和 JSON 在新式应用中仍发挥着重要作用。 构建 gRPC 和 JSON Web API 给应用开发增加了不必要的开销。

本文讨论如何使用 gRPC 服务创建 JSON Web API。

概述

gRPC JSON 转码是为 gRPC 服务创建 RESTful JSON API 的 ASP.NET Core 的扩展。 配置转码后,应用可以使用熟悉的 HTTP 概念调用 gRPC 服务:

  • HTTP 谓词
  • URL 参数绑定
  • JSON 请求/响应

gRPC 仍然可以用来调用服务。

注意

gRPC JSON 转码取代了 gRPC HTTP API,这是一个替代的实验性扩展。

使用情况

  1. 将包引用添加到 Microsoft.AspNetCore.Grpc.JsonTranscoding

  2. 通过添加 AddJsonTranscoding,在服务器启动代码中注册转码:在 Program.cs 文件中,将 builder.Services.AddGrpc(); 更改为 builder.Services.AddGrpc().AddJsonTranscoding();

  3. <IncludeHttpRuleProtos>true</IncludeHttpRuleProtos> 添加到项目文件 (.csproj) 的属性组:

    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <InvariantGlobalization>true</InvariantGlobalization>
        <IncludeHttpRuleProtos>true</IncludeHttpRuleProtos>
      </PropertyGroup>
    
  4. 用 HTTP 绑定和路由在 .proto 文件中注释 gRPC 方法:

    syntax = "proto3";
    
    option csharp_namespace = "GrpcServiceTranscoding";
    import "google/api/annotations.proto";
    
    package greet;
    
    // The greeting service definition.
    service Greeter {
      rpc SayHello (HelloRequest) returns (HelloReply) {
        option (google.api.http) = {
          get: "/v1/greeter/{name}"
        };
      }
    }
    
    // The request message containing the user's name.
    message HelloRequest {
      string name = 1;
    }
    
    // The response message containing the greetings.
    message HelloReply {
      string message = 1;
    }
    

SayHello gRPC 方法现在可以作为 gRPC 和 JSON Web API 调用:

  • 请求: GET /v1/greeter/world
  • 响应: { "message": "Hello world" }

如果服务器配置为针对每个请求写入日志,则服务器日志显示 gRPC 服务执行 HTTP 调用。 转码将传入的 HTTP 请求映射到 gRPC 消息,然后将响应消息转换为 JSON。

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/1.1 GET https://localhost:5001/v1/greeter/world
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint 'gRPC - /v1/greeter/{name}'
info: Server.GreeterService[0]
      Sending hello to world
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'gRPC - /v1/greeter/{name}'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 1.996ms 200 application/json

批注 gRPC 方法

gRPC 方法必须在支持转码之前使用 HTTP 规则进行注释。 HTTP 规则包括有关如何调用 gRPC 方法的信息,例如 HTTP 方法和路由。

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/v1/greeter/{name}"
    };
  }
}

继续浏览示例:

  • 使用 SayHello 方法定义 Greeter 服务。 该方法具有使用名称 google.api.http 指定的 HTTP 规则。
  • 可以使用 GET 请求和 /v1/greeter/{name} 路由访问该方法。
  • 请求消息上的 name 字段绑定到路由参数。

许多选项可用于自定义 gRPC 方法如何绑定到 RESTful API。 有关批注 gRPC 方法和自定义 JSON 的详细信息,请参阅为 gRPC JSON 转码配置 HTTP 和 JSON

流式处理方法

HTTP/2 上的传统 gRPC 支持所有方向的流式处理。 转码仅限于服务器流式处理。 不支持客户端流式处理和双向流式处理方法。

服务器流式处理方法使用以行分隔的 JSON。 使用 WriteAsync 的每条消息都会序列化为 JSON,后跟一个新行。

以下服务器流式处理方法写入三条消息:

public override async Task StreamingFromServer(ExampleRequest request,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    for (var i = 1; i <= 3; i++)
    {
        await responseStream.WriteAsync(new ExampleResponse { Text = $"Message {i}" });
        await Task.Delay(TimeSpan.FromSeconds(1));
    }
}

客户端接收三个以行分隔的 JSON 对象:

{"Text":"Message 1"}
{"Text":"Message 2"}
{"Text":"Message 3"}

请注意,WriteIndentedJSON 设置不适用于服务器流式处理方法。 整齐打印将新行和空格添加到 JSON,这不能与以行分隔的 JSON 一起使用。

查看或下载 ASP.NET Core gPRC 转码和流式处理应用示例

HTTP 协议

.NET SDK 中包含的 ASP.NET Core gRPC 服务模板创建仅针对 HTTP/2 配置的应用。 当应用仅支持传统的 gRPC over HTTP/2 时,HTTP/2 是很好的默认设置。 但是,转码同时适用于 HTTP/1.1 和 HTTP/2。 某些平台(如 UWP 或 Unity)无法使用 HTTP/2。 若要支持所有客户端应用,请将服务器配置为启用 HTTP/1.1 和 HTTP/2。

更新 appsettings.json 中的默认协议:

{
  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http1AndHttp2"
    }
  }
}

或者,在启动代码中配置 Kestrel 终结点

在同一端口上启用 HTTP/1.1 和 HTTP/2 需要 TLS 进行协议协商。 有关在 gRPC 应用中配置 HTTP 协议的详细信息,请参阅 ASP.NET Core gRPC 协议协商

gRPC JSON 转码与 gRPC-Web

转码和 gRPC-Web 都支持从浏览器调用 gRPC 服务。 但是,它们的操作方式是不同的:

  • gRPC-Web 允许浏览器应用通过 gRPC-Web 客户端和 Protobuf 从浏览器调用 gRPC 服务。 gRPC-Web 需要浏览器应用生成 gRPC 客户端,并且具有快速发送小型 Protobuf 消息的优点。
  • 转码允许浏览器应用调用 gRPC 服务,就像它们是使用 JSON 的 RESTful API 一样。 浏览器应用不需要生成 gRPC 客户端或了解 gRPC 的任何信息。

可以使用浏览器 JavaScript API 调用以前的 Greeter 服务:

var name = nameInput.value;

fetch('/v1/greeter/' + name)
  .then((response) => response.json())
  .then((result) => {
    console.log(result.message);
    // Hello world
  });

grpc-gateway

grpc-gateway 是从 gRPC 服务创建 RESTful JSON API 的另一种技术。 它使用相同的 .proto 注释将 HTTP 概念映射到 gRPC 服务。

grpc-gateway 使用代码生成来创建反向代理服务器。 反向代理将 RESTful 调用转换为 gRPC+Protobuf,然后通过 HTTP/2 将调用发送到 gRPC 服务。 这种方法的优点是 gRPC 服务不知道 RESTful JSON API。 任何 gRPC 服务器都可以使用 grpc-gateway。

同时,gRPC JSON 转码在 ASP.NET Core 应用内运行。 它将 JSON 反序列化为 Protobuf 消息,然后直接调用 gRPC 服务。 ASP.NET Core 中的转码为 .NET 应用开发人员提供了以下优势:

  • 复杂性更低:gRPC 服务和映射的 RESTful JSON API 都在同一个 ASP.NET Core 应用外部运行。
  • 性能更佳:转码将 JSON 反序列化为 Protobuf 消息,并直接调用 gRPC 服务。 与对不同的服务器进行新的 gRPC 调用相比,执行此进程内操作时性能有显著的优势。
  • 成本更低:减少了服务器数量,降低了每月托管费用。

如需了解 grpc-gateway 的安装和使用,请参阅 grpc-gateway 自述文件

其他资源

gRPC 是一种高性能远程过程调用 (RPC) 框架。 gRPC 使用 HTTP/2、流式传输、Protobuf 和消息协定来创建高性能的实时服务。

gRPC 有一个限制,即不是所有平台都可以使用它。 浏览器并不完全支持 HTTP/2,这使得 REST API 和 JSON 成为将数据引入浏览器应用的主要方式。 尽管 gRPC 带来了很多好处,REST API 和 JSON 在新式应用中仍发挥着重要作用。 构建 gRPC 和 JSON Web API 给应用开发增加了不必要的开销。

本文讨论如何使用 gRPC 服务创建 JSON Web API。

概述

gRPC JSON 转码是为 gRPC 服务创建 RESTful JSON API 的 ASP.NET Core 的扩展。 配置转码后,应用可以使用熟悉的 HTTP 概念调用 gRPC 服务:

  • HTTP 谓词
  • URL 参数绑定
  • JSON 请求/响应

gRPC 仍然可以用来调用服务。

注意

gRPC JSON 转码取代了 gRPC HTTP API,这是一个替代的实验性扩展。

使用情况

  1. 将包引用添加到 Microsoft.AspNetCore.Grpc.JsonTranscoding
  2. 通过添加 AddJsonTranscoding,在服务器启动代码中注册转码:在 Program.cs 文件中,将 builder.Services.AddGrpc(); 更改为 builder.Services.AddGrpc().AddJsonTranscoding();
  3. 在包含 .csproj 文件的项目目录中创建目录结构 /google/api
  4. google/api/http.protogoogle/api/annotations.proto 文件添加到 /google/api 目录中。
  5. 用 HTTP 绑定和路由在 .proto 文件中注释 gRPC 方法:
syntax = "proto3";

option csharp_namespace = "GrpcServiceTranscoding";
import "google/api/annotations.proto";

package greet;

// The greeting service definition.
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/v1/greeter/{name}"
    };
  }
}

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

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

SayHello gRPC 方法现在可以作为 gRPC 和 JSON Web API 调用:

  • 请求: GET /v1/greeter/world
  • 响应: { "message": "Hello world" }

如果服务器配置为针对每个请求写入日志,则服务器日志显示 gRPC 服务执行 HTTP 调用。 转码将传入的 HTTP 请求映射到 gRPC 消息,然后将响应消息转换为 JSON。

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/1.1 GET https://localhost:5001/v1/greeter/world
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint 'gRPC - /v1/greeter/{name}'
info: Server.GreeterService[0]
      Sending hello to world
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'gRPC - /v1/greeter/{name}'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 1.996ms 200 application/json

批注 gRPC 方法

gRPC 方法必须在支持转码之前使用 HTTP 规则进行注释。 HTTP 规则包括有关如何调用 gRPC 方法的信息,例如 HTTP 方法和路由。

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/v1/greeter/{name}"
    };
  }
}

继续浏览示例:

  • 使用 SayHello 方法定义 Greeter 服务。 该方法具有使用名称 google.api.http 指定的 HTTP 规则。
  • 可以使用 GET 请求和 /v1/greeter/{name} 路由访问该方法。
  • 请求消息上的 name 字段绑定到路由参数。

许多选项可用于自定义 gRPC 方法如何绑定到 RESTful API。 有关批注 gRPC 方法和自定义 JSON 的详细信息,请参阅为 gRPC JSON 转码配置 HTTP 和 JSON

流式处理方法

HTTP/2 上的传统 gRPC 支持所有方向的流式处理。 转码仅限于服务器流式处理。 不支持客户端流式处理和双向流式处理方法。

服务器流式处理方法使用以行分隔的 JSON。 使用 WriteAsync 的每条消息都会序列化为 JSON,后跟一个新行。

以下服务器流式处理方法写入三条消息:

public override async Task StreamingFromServer(ExampleRequest request,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    for (var i = 1; i <= 3; i++)
    {
        await responseStream.WriteAsync(new ExampleResponse { Text = $"Message {i}" });
        await Task.Delay(TimeSpan.FromSeconds(1));
    }
}

客户端接收三个以行分隔的 JSON 对象:

{"Text":"Message 1"}
{"Text":"Message 2"}
{"Text":"Message 3"}

请注意,WriteIndentedJSON 设置不适用于服务器流式处理方法。 整齐打印将新行和空格添加到 JSON,这不能与以行分隔的 JSON 一起使用。

查看或下载 ASP.NET Core gPRC 转码和流式处理应用示例

HTTP 协议

.NET SDK 中包含的 ASP.NET Core gRPC 服务模板创建仅针对 HTTP/2 配置的应用。 当应用仅支持传统的 gRPC over HTTP/2 时,HTTP/2 是很好的默认设置。 但是,转码同时适用于 HTTP/1.1 和 HTTP/2。 某些平台(如 UWP 或 Unity)无法使用 HTTP/2。 若要支持所有客户端应用,请将服务器配置为启用 HTTP/1.1 和 HTTP/2。

更新 appsettings.json 中的默认协议:

{
  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http1AndHttp2"
    }
  }
}

或者,在启动代码中配置 Kestrel 终结点

在同一端口上启用 HTTP/1.1 和 HTTP/2 需要 TLS 进行协议协商。 有关在 gRPC 应用中配置 HTTP 协议的详细信息,请参阅 ASP.NET Core gRPC 协议协商

gRPC JSON 转码与 gRPC-Web

转码和 gRPC-Web 都支持从浏览器调用 gRPC 服务。 但是,它们的操作方式是不同的:

  • gRPC-Web 允许浏览器应用通过 gRPC-Web 客户端和 Protobuf 从浏览器调用 gRPC 服务。 gRPC-Web 需要浏览器应用生成 gRPC 客户端,并且具有快速发送小型 Protobuf 消息的优点。
  • 转码允许浏览器应用调用 gRPC 服务,就像它们是使用 JSON 的 RESTful API 一样。 浏览器应用不需要生成 gRPC 客户端或了解 gRPC 的任何信息。

可以使用浏览器 JavaScript API 调用以前的 Greeter 服务:

var name = nameInput.value;

fetch('/v1/greeter/' + name)
  .then((response) => response.json())
  .then((result) => {
    console.log(result.message);
    // Hello world
  });

grpc-gateway

grpc-gateway 是从 gRPC 服务创建 RESTful JSON API 的另一种技术。 它使用相同的 .proto 注释将 HTTP 概念映射到 gRPC 服务。

grpc-gateway 使用代码生成来创建反向代理服务器。 反向代理将 RESTful 调用转换为 gRPC+Protobuf,然后通过 HTTP/2 将调用发送到 gRPC 服务。 这种方法的优点是 gRPC 服务不知道 RESTful JSON API。 任何 gRPC 服务器都可以使用 grpc-gateway。

同时,gRPC JSON 转码在 ASP.NET Core 应用内运行。 它将 JSON 反序列化为 Protobuf 消息,然后直接调用 gRPC 服务。 ASP.NET Core 中的转码为 .NET 应用开发人员提供了以下优势:

  • 复杂性更低:gRPC 服务和映射的 RESTful JSON API 都在同一个 ASP.NET Core 应用外部运行。
  • 性能更佳:转码将 JSON 反序列化为 Protobuf 消息,并直接调用 gRPC 服务。 与对不同的服务器进行新的 gRPC 调用相比,执行此进程内操作时性能有显著的优势。
  • 成本更低:减少了服务器数量,降低了每月托管费用。

如需了解 grpc-gateway 的安装和使用,请参阅 grpc-gateway 自述文件

其他资源