对 .NET 上的 gRPC 进行故障排除

作者:James Newton-King

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

本文档讨论了在 .NET 上开发 gRPC 应用时经常遇到的问题。

客户端和服务 SSL/TLS 配置不匹配

默认情况下,gRPC 模板和示例使用传输层安全性 (TLS) 来保护 gRPC 服务。 gRPC 客户端需要使用安全连接才能成功调用受保护的 gRPC 服务。

可在应用启动时验证 ASP.NET Core gRPC 服务是否正在使用 TLS。 该服务将在 HTTPS 终结点上侦听:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 客户端必须在服务器地址中使用 https 才能使用安全连接进行调用:

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);

所有 gRPC 客户端实现都支持 TLS。 其他语言的 gRPC 客户端通常需要配置有 SslCredentials 的通道。 SslCredentials 指定客户端将使用的证书,必须使用该证书,而不是使用不安全凭据。 有关配置不同 gRPC 客户端实现以使用 TLS 的示例,请参阅 gRPC 身份验证

使用不受信任/无效证书调用 gRPC 服务

.NET gRPC 客户端要求服务具有受信任的证书。 在没有受信任证书的情况下调用 gRPC 服务时,将返回以下错误消息:

未经处理的异常。 System.Net.Http.HttpRequestException:无法建立 SSL 连接,请查看内部异常。 ---> System.Security.Authentication.AuthenticationException:根据验证过程,远程证书无效。

如果在本地测试应用且 ASP.NET Core HTTPS 开发证书不受信任,则可能会显示此错误。 有关解决此问题的说明,请参阅在 Windows 和 macOS 上信任 ASP.NET Core HTTPS 开发证书

如果要在另一台计算机上调用 gRPC 服务且无法信任该证书,则可以将 gRPC 客户端配置为忽略无效的证书。 下面的代码使用 HttpClientHandler.ServerCertificateCustomValidationCallback 来允许在没有受信任证书的情况下进行调用:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

gRPC 客户端工厂允许在没有受信任证书的情况下调用。 使用 ConfigurePrimaryHttpMessageHandler 扩展方法在客户端上配置处理程序:


var services = new ServiceCollection();

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback =
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的证书只应在应用开发过程中使用。 生产应用应始终使用有效的证书。

使用 .NET Core 客户端调用不安全的 gRPC 服务

.NET gRPC 可以通过在服务器地址中指定 http 来调用不安全的 gRPC 服务。 例如,GrpcChannel.ForAddress("http://localhost:5000")

根据应用使用的 .NET 版本,调用不安全的 gRPC 服务还需要满足一些额外要求:

重要事项

必须在仅 HTTP/2 端口上托管不安全的 gRPC 服务。 有关详细信息,请参阅 ASP.NET Core 协议协商

无法在 macOS 上启动 ASP.NET Core gRPC 应用

Kestrel 不支持在使用 .NET 8 之前版本的 macOS 上使用带 TLS 的 HTTP/2。 默认情况下,ASP.NET Core gRPC 模板和示例使用 TLS。 尝试启动 gRPC 服务器时,你将看到以下错误消息:

无法绑定到 IPv4 环回接口上的 https://localhost:5001 :“由于缺少 ALPN 支持,macOS 不支持使用 TLS 的 HTTP/2。”。

若要解决 .NET 7 及更早版本中的此问题,请将 Kestrel 和 gRPC 客户端配置为使用不带有 TLS 的 HTTP/2。 应仅在开发过程中执行此操作。 如果不使用 TLS,将会在不加密的情况下发送 gRPC 消息。 有关详细信息,请参阅 Asp.Net Core 7.0:无法在 macOS 上启用 ASP.NET Core gRPC 应用

gRPC C# 资产不是从 .proto 文件生成的代码

具体客户端和服务基类的 gRPC 代码生成需要从项目引用 protobuf 文件和工具。 必须包括:

有关生成 gRPC C# 资产的详细信息,请参阅使用 C# 的 gRPC 服务

托管 gRPC 服务的 ASP.NET Core web 应用仅需要已生成的服务基类:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

发出 gRPC 调用的 gRPC 客户端应用仅需要已生成的具体客户端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 项目无法从 .proto 文件生成 gRPC C# 资产

WPF 项目存在一个已知问题,其阻止 gRPC 代码生成正常运行。 在 WPF 项目中通过引用 Grpc.Tools.proto 文件生成的任何 gRPC 类型在使用时都将创建编译错误:

错误 CS0246:找不到类型名称或命名空间名称 ’MyGrpcServices’ (是否缺少 using 指令或程序集引用?)

可以通过以下方式解决此问题:

  1. 创建新的 .NET Core 类库项目。
  2. 在新项目中,添加引用以.proto 文件启用 C# 代码生成
  3. 在 WPF 应用程序中,添加对新项目的引用。

WPF 应用程序可以使用来自新类库项目的 gRPC 生成的类型。

调用子目录中托管的 gRPC 服务

警告

许多第三方 gRPC 工具不支持子目录中托管的服务。 请考虑找到将 gRPC 托管为根目录的方法。

发出 gRPC 调用时,将忽略 gRPC 通道地址的路径部分。 例如,当路由对服务的 gRPC 调用时,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不使用 ignored_path

由于 gRPC 具有标准化的规范地址结构,因此会忽略地址路径。 gRPC 地址组合了包、服务和方法的名称:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情况下,应用需要包含具有 gRPC 调用的路径。 例如,当 ASP.NET Core gRPC 应用托管在 IIS 目录中时,需要在请求中包含该目录。 如果路径是必需的,则可以使用下面指定的自定义 SubdirectoryHandler 方法将其添加到 gRPC 调用:

public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 在创建 gRPC 通道时使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

var reply = await client.SayHelloAsync(
                  new HelloRequest { Name = "GreeterClient" });

前面的代码:

  • 创建具有 /MyApp 路径的 SubdirectoryHandler
  • 配置通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 调用 gRPC 服务。 gRPC 调用将发送到 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,可以使用 AddHttpMessageHandler 为客户端工厂配置 SubdirectoryHandler

将 gRPC 客户端配置为使用 HTTP/3

.NET gRPC 客户端支持使用 .NET 6 或更高版本的 HTTP/3。 如果服务器向客户端发送表明服务器支持 HTTP/3 的 alt-svc 响应标头,则客户端将自动将其连接升级到 HTTP/3。 有关详细信息,请参阅对 ASP.NET Core Kestrel Web 服务器使用 HTTP/3

DelegatingHandler 可用于强制 gRPC 客户端使用 HTTP/3。 强制使用 HTTP/3 可以避免升级请求的开销。 使用类似于下面的代码强制使用 HTTP/3:

public class Http3Handler : DelegatingHandler
{
    public Http3Handler() { }
    public Http3Handler(HttpMessageHandler innerHandler) : base(innerHandler) { }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Version = HttpVersion.Version30;
        request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;

        return base.SendAsync(request, cancellationToken);
    }
}

Http3Handler 在创建 gRPC 通道时使用。 以下代码创建了一个配置为使用 Http3Handler 的通道。

var handler = new Http3Handler(new HttpClientHandler());

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

var reply = await client.SayHelloAsync(
                  new HelloRequest { Name = "GreeterClient" });

或者,可以使用 AddHttpMessageHandler 为客户端工厂配置 Http3Handler

在 Alpine Linux 上构建 gRPC

Grpc.Tools 包使用称为 protoc 的捆绑的本机二进制文件.proto 文件生成 .NET 类型。 在 Grpc.Tools 中的本机二进制文件(如 Alpine Linux)不支持的平台上构建 gRPC 应用还需执行其他步骤。

提前生成代码

一种解决方案可提前生成代码。

  1. .proto 文件和 Grpc.Tools 包引用移动到新项目。
  2. 将项目作为 NuGet 包发布,然后将其上传到 NuGet 源。
  3. 更新应用以引用 NuGet 包。

通过上述步骤,应用不再需要 Grpc.Tools 来构建,因为代码可提前生成。

自定义 Grpc.Tools 本机二进制文件

Grpc.Tools 支持使用自定义本机二进制文件。 此功能允许 gRPC 工具在其捆绑的本机二进制文件不支持的环境中运行。

生成或获取 protocgrpc_csharp_plugin 本机二进制文件,并配置 Grpc.Tools 以使用它们。 通过设置以下环境变量来配置本机二进制文件:

  • PROTOBUF_PROTOC - 协议缓冲区编译器的完整路径
  • GRPC_PROTOC_PLUGIN - grpc_csharp_plugin 的完整路径

对于 Alpine Linux,https://pkgs.alpinelinux.org/ 上有社区提供的用于协议缓冲区编译器和 gRPC 插件的包。

# Build or install the binaries for your architecture.

# e.g. for Alpine Linux the grpc-plugins package can be used
#  See https://pkgs.alpinelinux.org/package/edge/community/x86_64/grpc-plugins
apk add grpc-plugins  # Alpine Linux specific package installer

# Set environment variables for the built/installed protoc
# and grpc_csharp_plugin binaries
export PROTOBUF_PROTOC=/usr/bin/protoc
export GRPC_PROTOC_PLUGIN=/usr/bin/grpc_csharp_plugin

# When dotnet build runs, the Grpc.Tools NuGet package
# uses the binaries pointed to by the environment variables.
dotnet build

有关将 Grpc.Tools 与不受支持的体系结构配合使用的详细信息,请参阅 gRPC 构建集成文档

本文档讨论了在 .NET 上开发 gRPC 应用时经常遇到的问题。

客户端和服务 SSL/TLS 配置不匹配

默认情况下,gRPC 模板和示例使用传输层安全性 (TLS) 来保护 gRPC 服务。 gRPC 客户端需要使用安全连接才能成功调用受保护的 gRPC 服务。

可在应用启动时验证 ASP.NET Core gRPC 服务是否正在使用 TLS。 该服务将在 HTTPS 终结点上侦听:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 客户端必须在服务器地址中使用 https 才能使用安全连接进行调用:

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 Greet.GreeterClient(channel);
}

所有 gRPC 客户端实现都支持 TLS。 其他语言的 gRPC 客户端通常需要配置有 SslCredentials 的通道。 SslCredentials 指定客户端将使用的证书,必须使用该证书,而不是使用不安全凭据。 有关配置不同 gRPC 客户端实现以使用 TLS 的示例,请参阅 gRPC 身份验证

使用不受信任/无效证书调用 gRPC 服务

.NET gRPC 客户端要求服务具有受信任的证书。 在没有受信任证书的情况下调用 gRPC 服务时,将返回以下错误消息:

未经处理的异常。 System.Net.Http.HttpRequestException:无法建立 SSL 连接,请查看内部异常。 ---> System.Security.Authentication.AuthenticationException:根据验证过程,远程证书无效。

如果在本地测试应用且 ASP.NET Core HTTPS 开发证书不受信任,则可能会显示此错误。 有关解决此问题的说明,请参阅在 Windows 和 macOS 上信任 ASP.NET Core HTTPS 开发证书

如果要在另一台计算机上调用 gRPC 服务且无法信任该证书,则可以将 gRPC 客户端配置为忽略无效的证书。 下面的代码使用 HttpClientHandler.ServerCertificateCustomValidationCallback 来允许在没有受信任证书的情况下进行调用:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

gRPC 客户端工厂允许在没有受信任证书的情况下调用。 使用 ConfigurePrimaryHttpMessageHandler 扩展方法在客户端上配置处理程序:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = 
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的证书只应在应用开发过程中使用。 生产应用应始终使用有效的证书。

使用 .NET Core 客户端调用不安全的 gRPC 服务

.NET gRPC 可以通过在服务器地址中指定 http 来调用不安全的 gRPC 服务。 例如,GrpcChannel.ForAddress("http://localhost:5000")

根据应用使用的 .NET 版本,调用不安全的 gRPC 服务还需要满足一些额外要求:

  • .NET 5 或更高版本要求使用 Grpc.Net.Client 版本 2.32.0 或更高版本。

  • .NET Core 3.x 要求进行额外配置。 应用必须将 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 开关设置为 true

    // This switch must be set before creating the GrpcChannel/HttpClient.
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
    // The port number(5000) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("http://localhost:5000");
    var client = new Greet.GreeterClient(channel);
    

只有 .NET Core 3.x 需要 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 开关。 .NET 5 中不需要任何额外配置,也没有这项要求。

重要

必须在仅 HTTP/2 端口上托管不安全的 gRPC 服务。 有关详细信息,请参阅 ASP.NET Core 协议协商

无法在 macOS 上启动 ASP.NET Core gRPC 应用

Kestrel 不支持在使用 .NET 8 之前版本的 macOS 上使用带 TLS 的 HTTP/2。 默认情况下,ASP.NET Core gRPC 模板和示例使用 TLS。 尝试启动 gRPC 服务器时,你将看到以下错误消息:

无法绑定到 IPv4 环回接口上的 https://localhost:5001 :“由于缺少 ALPN 支持,macOS 不支持使用 TLS 的 HTTP/2。”。

若要解决 .NET 7 及更早版本中的此问题,请将 Kestrel 和 gRPC 客户端配置为使用不带有 TLS 的 HTTP/2。 应仅在开发过程中执行此操作。 如果不使用 TLS,将会在不加密的情况下发送 gRPC 消息。

Kestrel 必须在 Program.cs 中配置一个不带 TLS 的 HTTP/2 终结点:

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    // Setup a HTTP/2 endpoint without TLS.
    options.ListenLocalhost(<5287>, o => o.Protocols =
        HttpProtocols.Http2);
});
  • 在前面的代码中,将 localhost 端口号 5287 替换为在 gRPC 服务项目的 Properties/launchSettings.json 中指定的 HTTP(而不是 HTTPS)端口号。

如果在未带有 TLS 的情况下配置了 HTTP/2 终结点,则终结点的 ListenOptions.Protocols 必须设置为 HttpProtocols.Http2。 无法使用 HttpProtocols.Http1AndHttp2,因为需要使用 TLS 来协商 HTTP/2。 如果未带有 TLS,则与终结点的所有连接均默认为 HTTP/1.1,且 gRPC 调用会失败。

还必须将 gRPC 客户端配置为不使用 TLS。 有关详细信息,请参阅使用 .NET Core 客户端调用不安全的 gRPC 服务

警告

应仅在应用开发过程中使用不带有 TLS 的 HTTP/2。 生产应用应始终使用传输安全性。 有关详细信息,请参阅适用于 ASP.NET Core 的 gRPC 的安全注意事项

gRPC C# 资产不是从 .proto 文件生成的代码

具体客户端和服务基类的 gRPC 代码生成需要从项目引用 protobuf 文件和工具。 必须包括:

有关生成 gRPC C# 资产的详细信息,请参阅使用 C# 的 gRPC 服务

托管 gRPC 服务的 ASP.NET Core web 应用仅需要已生成的服务基类:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

发出 gRPC 调用的 gRPC 客户端应用仅需要已生成的具体客户端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 项目无法从 .proto 文件生成 gRPC C# 资产

WPF 项目存在一个已知问题,其阻止 gRPC 代码生成正常运行。 在 WPF 项目中通过引用 Grpc.Tools.proto 文件生成的任何 gRPC 类型在使用时都将创建编译错误:

错误 CS0246:找不到类型名称或命名空间名称 ’MyGrpcServices’ (是否缺少 using 指令或程序集引用?)

可以通过以下方式解决此问题:

  1. 创建新的 .NET Core 类库项目。
  2. 在新项目中,添加引用以.proto 文件启用 C# 代码生成
  3. 在 WPF 应用程序中,添加对新项目的引用。

WPF 应用程序可以使用来自新类库项目的 gRPC 生成的类型。

调用子目录中托管的 gRPC 服务

警告

许多第三方 gRPC 工具不支持子目录中托管的服务。 请考虑找到将 gRPC 托管为根目录的方法。

发出 gRPC 调用时,将忽略 gRPC 通道地址的路径部分。 例如,当路由对服务的 gRPC 调用时,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不使用 ignored_path

由于 gRPC 具有标准化的规范地址结构,因此会忽略地址路径。 gRPC 地址组合了包、服务和方法的名称:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情况下,应用需要包含具有 gRPC 调用的路径。 例如,当 ASP.NET Core gRPC 应用托管在 IIS 目录中时,需要在请求中包含该目录。 如果路径是必需的,则可以使用下面指定的自定义 SubdirectoryHandler 方法将其添加到 gRPC 调用:

/// <summary>
/// A delegating handler that adds a subdirectory to the URI of gRPC requests.
/// </summary>
public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 在创建 gRPC 通道时使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

前面的代码:

  • 创建具有 /MyApp 路径的 SubdirectoryHandler
  • 配置通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 调用 gRPC 服务。 gRPC 调用将发送到 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,可以使用 AddHttpMessageHandler 为客户端工厂配置 SubdirectoryHandler

将 gRPC 客户端配置为使用 HTTP/3

.NET gRPC 客户端支持使用 .NET 6 或更高版本的 HTTP/3。 如果服务器向客户端发送表明服务器支持 HTTP/3 的 alt-svc 响应标头,则客户端将自动将其连接升级到 HTTP/3。 有关如何在服务器上启用 HTTP/3 的信息,请参阅将 HTTP/3 与 ASP.NET Core Kestrel Web 服务器结合使用

默认情况下,.NET 8 中的 HTTP/3 支持处于启用状态。 .NET 6 和 .NET 7 中的 HTTP/3 支持需要通过项目文件中的一个配置标志启用:

<ItemGroup>
  <RuntimeHostConfigurationOption Include="System.Net.SocketsHttpHandler.Http3Support" Value="true" />
</ItemGroup>

也可以使用 AppContext.SetSwitch 设置 System.Net.SocketsHttpHandler.Http3Support

DelegatingHandler 可用于强制 gRPC 客户端使用 HTTP/3。 强制使用 HTTP/3 可以避免升级请求的开销。 使用类似于下面的代码强制使用 HTTP/3:

/// <summary>
/// A delegating handler that changes the request HTTP version to HTTP/3.
/// </summary>
public class Http3Handler : DelegatingHandler
{
    public Http3Handler() { }
    public Http3Handler(HttpMessageHandler innerHandler) : base(innerHandler) { }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Version = HttpVersion.Version30;
        request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;

        return base.SendAsync(request, cancellationToken);
    }
}

Http3Handler 在创建 gRPC 通道时使用。 以下代码创建了一个配置为使用 Http3Handler 的通道。

var handler = new Http3Handler(new HttpClientHandler());

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

或者,可以使用 AddHttpMessageHandler 为客户端工厂配置 Http3Handler

在 Alpine Linux 上构建 gRPC

Grpc.Tools 包使用称为 protoc 的捆绑的本机二进制文件.proto 文件生成 .NET 类型。 在 Grpc.Tools 中的本机二进制文件(如 Alpine Linux)不支持的平台上构建 gRPC 应用还需执行其他步骤。

提前生成代码

一种解决方案可提前生成代码。

  1. .proto 文件和 Grpc.Tools 包引用移动到新项目。
  2. 将项目作为 NuGet 包发布,然后将其上传到 NuGet 源。
  3. 更新应用以引用 NuGet 包。

通过上述步骤,应用不再需要 Grpc.Tools 来构建,因为代码可提前生成。

自定义 Grpc.Tools 本机二进制文件

Grpc.Tools 支持使用自定义本机二进制文件。 此功能允许 gRPC 工具在其捆绑的本机二进制文件不支持的环境中运行。

生成或获取 protocgrpc_csharp_plugin 本机二进制文件,并配置 Grpc.Tools 以使用它们。 通过设置以下环境变量来配置本机二进制文件:

  • PROTOBUF_PROTOC - 协议缓冲区编译器的完整路径
  • GRPC_PROTOC_PLUGIN - grpc_csharp_plugin 的完整路径

对于 Alpine Linux,https://pkgs.alpinelinux.org/ 上有社区提供的用于协议缓冲区编译器和 gRPC 插件的包。

# Build or install the binaries for your architecture.

# e.g. for Alpine Linux the grpc-plugins package can be used
#  See https://pkgs.alpinelinux.org/package/edge/community/x86_64/grpc-plugins
apk add grpc-plugins  # Alpine Linux specific package installer

# Set environment variables for the built/installed protoc
# and grpc_csharp_plugin binaries
export PROTOBUF_PROTOC=/usr/bin/protoc
export GRPC_PROTOC_PLUGIN=/usr/bin/grpc_csharp_plugin

# When dotnet build runs, the Grpc.Tools NuGet package
# uses the binaries pointed to by the environment variables.
dotnet build

有关将 Grpc.Tools 与不受支持的体系结构配合使用的详细信息,请参阅 gRPC 构建集成文档

本文档讨论了在 .NET 上开发 gRPC 应用时经常遇到的问题。

客户端和服务 SSL/TLS 配置不匹配

默认情况下,gRPC 模板和示例使用传输层安全性 (TLS) 来保护 gRPC 服务。 gRPC 客户端需要使用安全连接才能成功调用受保护的 gRPC 服务。

可在应用启动时验证 ASP.NET Core gRPC 服务是否正在使用 TLS。 该服务将在 HTTPS 终结点上侦听:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 客户端必须在服务器地址中使用 https 才能使用安全连接进行调用:

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 Greet.GreeterClient(channel);
}

所有 gRPC 客户端实现都支持 TLS。 其他语言的 gRPC 客户端通常需要配置有 SslCredentials 的通道。 SslCredentials 指定客户端将使用的证书,必须使用该证书,而不是使用不安全凭据。 有关配置不同 gRPC 客户端实现以使用 TLS 的示例,请参阅 gRPC 身份验证

使用不受信任/无效证书调用 gRPC 服务

.NET gRPC 客户端要求服务具有受信任的证书。 在没有受信任证书的情况下调用 gRPC 服务时,将返回以下错误消息:

未经处理的异常。 System.Net.Http.HttpRequestException:无法建立 SSL 连接,请查看内部异常。 ---> System.Security.Authentication.AuthenticationException:根据验证过程,远程证书无效。

如果在本地测试应用且 ASP.NET Core HTTPS 开发证书不受信任,则可能会显示此错误。 有关解决此问题的说明,请参阅在 Windows 和 macOS 上信任 ASP.NET Core HTTPS 开发证书

如果要在另一台计算机上调用 gRPC 服务且无法信任该证书,则可以将 gRPC 客户端配置为忽略无效的证书。 下面的代码使用 HttpClientHandler.ServerCertificateCustomValidationCallback 来允许在没有受信任证书的情况下进行调用:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

gRPC 客户端工厂允许在没有受信任证书的情况下调用。 使用 ConfigurePrimaryHttpMessageHandler 扩展方法在客户端上配置处理程序:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = 
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的证书只应在应用开发过程中使用。 生产应用应始终使用有效的证书。

使用 .NET Core 客户端调用不安全的 gRPC 服务

.NET gRPC 可以通过在服务器地址中指定 http 来调用不安全的 gRPC 服务。 例如,GrpcChannel.ForAddress("http://localhost:5000")

根据应用使用的 .NET 版本,调用不安全的 gRPC 服务还需要满足一些额外要求:

  • .NET 5 或更高版本要求使用 Grpc.Net.Client 版本 2.32.0 或更高版本。

  • .NET Core 3.x 要求进行额外配置。 应用必须将 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 开关设置为 true

    // This switch must be set before creating the GrpcChannel/HttpClient.
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
    // The port number(5000) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("http://localhost:5000");
    var client = new Greet.GreeterClient(channel);
    

只有 .NET Core 3.x 需要 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 开关。 .NET 5 中不需要任何额外配置,也没有这项要求。

重要

必须在仅 HTTP/2 端口上托管不安全的 gRPC 服务。 有关详细信息,请参阅 ASP.NET Core 协议协商

无法在 macOS 上启动 ASP.NET Core gRPC 应用

Kestrel 不支持在使用 .NET 8 之前版本的 macOS 上使用带 TLS 的 HTTP/2。 默认情况下,ASP.NET Core gRPC 模板和示例使用 TLS。 尝试启动 gRPC 服务器时,你将看到以下错误消息:

无法绑定到 IPv4 环回接口上的 https://localhost:5001 :“由于缺少 ALPN 支持,macOS 不支持使用 TLS 的 HTTP/2。”。

若要解决 .NET 7 及更早版本中的此问题,请将 Kestrel 和 gRPC 客户端配置为使用不带有 TLS 的 HTTP/2。 应仅在开发过程中执行此操作。 如果不使用 TLS,将会在不加密的情况下发送 gRPC 消息。

Kestrel 必须在 Program.cs 中配置一个不带 TLS 的 HTTP/2 终结点:

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

如果在未带有 TLS 的情况下配置了 HTTP/2 终结点,则终结点的 ListenOptions.Protocols 必须设置为 HttpProtocols.Http2。 无法使用 HttpProtocols.Http1AndHttp2,因为需要使用 TLS 来协商 HTTP/2。 如果未带有 TLS,则与终结点的所有连接均默认为 HTTP/1.1,且 gRPC 调用会失败。

还必须将 gRPC 客户端配置为不使用 TLS。 有关详细信息,请参阅使用 .NET Core 客户端调用不安全的 gRPC 服务

警告

应仅在应用开发过程中使用不带有 TLS 的 HTTP/2。 生产应用应始终使用传输安全性。 有关详细信息,请参阅适用于 ASP.NET Core 的 gRPC 的安全注意事项

gRPC C# 资产不是从 .proto 文件生成的代码

具体客户端和服务基类的 gRPC 代码生成需要从项目引用 protobuf 文件和工具。 必须包括:

有关生成 gRPC C# 资产的详细信息,请参阅使用 C# 的 gRPC 服务

托管 gRPC 服务的 ASP.NET Core web 应用仅需要已生成的服务基类:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

发出 gRPC 调用的 gRPC 客户端应用仅需要已生成的具体客户端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 项目无法从 .proto 文件生成 gRPC C# 资产

WPF 项目存在一个已知问题,其阻止 gRPC 代码生成正常运行。 在 WPF 项目中通过引用 Grpc.Tools.proto 文件生成的任何 gRPC 类型在使用时都将创建编译错误:

错误 CS0246:找不到类型名称或命名空间名称 ’MyGrpcServices’ (是否缺少 using 指令或程序集引用?)

可以通过以下方式解决此问题:

  1. 创建新的 .NET Core 类库项目。
  2. 在新项目中,添加引用以.proto 文件启用 C# 代码生成
  3. 在 WPF 应用程序中,添加对新项目的引用。

WPF 应用程序可以使用来自新类库项目的 gRPC 生成的类型。

调用子目录中托管的 gRPC 服务

警告

许多第三方 gRPC 工具不支持子目录中托管的服务。 请考虑找到将 gRPC 托管为根目录的方法。

发出 gRPC 调用时,将忽略 gRPC 通道地址的路径部分。 例如,当路由对服务的 gRPC 调用时,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不使用 ignored_path

由于 gRPC 具有标准化的规范地址结构,因此会忽略地址路径。 gRPC 地址组合了包、服务和方法的名称:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情况下,应用需要包含具有 gRPC 调用的路径。 例如,当 ASP.NET Core gRPC 应用托管在 IIS 目录中时,需要在请求中包含该目录。 如果路径是必需的,则可以使用下面指定的自定义 SubdirectoryHandler 方法将其添加到 gRPC 调用:

/// <summary>
/// A delegating handler that adds a subdirectory to the URI of gRPC requests.
/// </summary>
public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 在创建 gRPC 通道时使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

前面的代码:

  • 创建具有 /MyApp 路径的 SubdirectoryHandler
  • 配置通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 调用 gRPC 服务。 gRPC 调用将发送到 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,可以使用 AddHttpMessageHandler 为客户端工厂配置 SubdirectoryHandler

本文档讨论了在 .NET 上开发 gRPC 应用时经常遇到的问题。

客户端和服务 SSL/TLS 配置不匹配

默认情况下,gRPC 模板和示例使用传输层安全性 (TLS) 来保护 gRPC 服务。 gRPC 客户端需要使用安全连接才能成功调用受保护的 gRPC 服务。

可在应用启动时验证 ASP.NET Core gRPC 服务是否正在使用 TLS。 该服务将在 HTTPS 终结点上侦听:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 客户端必须在服务器地址中使用 https 才能使用安全连接进行调用:

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 Greet.GreeterClient(channel);
}

所有 gRPC 客户端实现都支持 TLS。 其他语言的 gRPC 客户端通常需要配置有 SslCredentials 的通道。 SslCredentials 指定客户端将使用的证书,必须使用该证书,而不是使用不安全凭据。 有关配置不同 gRPC 客户端实现以使用 TLS 的示例,请参阅 gRPC 身份验证

使用不受信任/无效证书调用 gRPC 服务

.NET gRPC 客户端要求服务具有受信任的证书。 在没有受信任证书的情况下调用 gRPC 服务时,将返回以下错误消息:

未经处理的异常。 System.Net.Http.HttpRequestException:无法建立 SSL 连接,请查看内部异常。 ---> System.Security.Authentication.AuthenticationException:根据验证过程,远程证书无效。

如果在本地测试应用且 ASP.NET Core HTTPS 开发证书不受信任,则可能会显示此错误。 有关解决此问题的说明,请参阅在 Windows 和 macOS 上信任 ASP.NET Core HTTPS 开发证书

如果要在另一台计算机上调用 gRPC 服务且无法信任该证书,则可以将 gRPC 客户端配置为忽略无效的证书。 下面的代码使用 HttpClientHandler.ServerCertificateCustomValidationCallback 来允许在没有受信任证书的情况下进行调用:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

gRPC 客户端工厂允许在没有受信任证书的情况下调用。 使用 ConfigurePrimaryHttpMessageHandler 扩展方法在客户端上配置处理程序:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = 
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的证书只应在应用开发过程中使用。 生产应用应始终使用有效的证书。

使用 .NET Core 客户端调用不安全的 gRPC 服务

.NET gRPC 可以通过在服务器地址中指定 http 来调用不安全的 gRPC 服务。 例如,GrpcChannel.ForAddress("http://localhost:5000")

根据应用使用的 .NET 版本,调用不安全的 gRPC 服务还需要满足一些额外要求:

  • .NET 5 或更高版本要求使用 Grpc.Net.Client 版本 2.32.0 或更高版本。

  • .NET Core 3.x 要求进行额外配置。 应用必须将 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 开关设置为 true

    // This switch must be set before creating the GrpcChannel/HttpClient.
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
    // The port number(5000) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("http://localhost:5000");
    var client = new Greet.GreeterClient(channel);
    

只有 .NET Core 3.x 需要 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 开关。 .NET 5 中不需要任何额外配置,也没有这项要求。

重要

必须在仅 HTTP/2 端口上托管不安全的 gRPC 服务。 有关详细信息,请参阅 ASP.NET Core 协议协商

无法在 macOS 上启动 ASP.NET Core gRPC 应用

Kestrel 不支持在使用 .NET 8 之前版本的 macOS 上使用带 TLS 的 HTTP/2。 默认情况下,ASP.NET Core gRPC 模板和示例使用 TLS。 尝试启动 gRPC 服务器时,你将看到以下错误消息:

无法绑定到 IPv4 环回接口上的 https://localhost:5001 :“由于缺少 ALPN 支持,macOS 不支持使用 TLS 的 HTTP/2。”。

若要解决 .NET 7 及更早版本中的此问题,请将 Kestrel 和 gRPC 客户端配置为使用不带有 TLS 的 HTTP/2。 应仅在开发过程中执行此操作。 如果不使用 TLS,将会在不加密的情况下发送 gRPC 消息。

Kestrel 必须在 Program.cs 中配置一个不带 TLS 的 HTTP/2 终结点:

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

如果在未带有 TLS 的情况下配置了 HTTP/2 终结点,则终结点的 ListenOptions.Protocols 必须设置为 HttpProtocols.Http2。 无法使用 HttpProtocols.Http1AndHttp2,因为需要使用 TLS 来协商 HTTP/2。 如果未带有 TLS,则与终结点的所有连接均默认为 HTTP/1.1,且 gRPC 调用会失败。

还必须将 gRPC 客户端配置为不使用 TLS。 有关详细信息,请参阅使用 .NET Core 客户端调用不安全的 gRPC 服务

警告

应仅在应用开发过程中使用不带有 TLS 的 HTTP/2。 生产应用应始终使用传输安全性。 有关详细信息,请参阅适用于 ASP.NET Core 的 gRPC 的安全注意事项

gRPC C# 资产不是从 .proto 文件生成的代码

具体客户端和服务基类的 gRPC 代码生成需要从项目引用 protobuf 文件和工具。 必须包括:

有关生成 gRPC C# 资产的详细信息,请参阅使用 C# 的 gRPC 服务

托管 gRPC 服务的 ASP.NET Core web 应用仅需要已生成的服务基类:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

发出 gRPC 调用的 gRPC 客户端应用仅需要已生成的具体客户端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 项目无法从 .proto 文件生成 gRPC C# 资产

WPF 项目存在一个已知问题,其阻止 gRPC 代码生成正常运行。 在 WPF 项目中通过引用 Grpc.Tools.proto 文件生成的任何 gRPC 类型在使用时都将创建编译错误:

错误 CS0246:找不到类型名称或命名空间名称 ’MyGrpcServices’ (是否缺少 using 指令或程序集引用?)

可以通过以下方式解决此问题:

  1. 创建新的 .NET Core 类库项目。
  2. 在新项目中,添加引用以.proto 文件启用 C# 代码生成
  3. 在 WPF 应用程序中,添加对新项目的引用。

WPF 应用程序可以使用来自新类库项目的 gRPC 生成的类型。

调用子目录中托管的 gRPC 服务

警告

许多第三方 gRPC 工具不支持子目录中托管的服务。 请考虑找到将 gRPC 托管为根目录的方法。

发出 gRPC 调用时,将忽略 gRPC 通道地址的路径部分。 例如,当路由对服务的 gRPC 调用时,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不使用 ignored_path

由于 gRPC 具有标准化的规范地址结构,因此会忽略地址路径。 gRPC 地址组合了包、服务和方法的名称:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情况下,应用需要包含具有 gRPC 调用的路径。 例如,当 ASP.NET Core gRPC 应用托管在 IIS 目录中时,需要在请求中包含该目录。 如果路径是必需的,则可以使用下面指定的自定义 SubdirectoryHandler 方法将其添加到 gRPC 调用:

/// <summary>
/// A delegating handler that adds a subdirectory to the URI of gRPC requests.
/// </summary>
public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 在创建 gRPC 通道时使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

前面的代码:

  • 创建具有 /MyApp 路径的 SubdirectoryHandler
  • 配置通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 调用 gRPC 服务。 gRPC 调用将发送到 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,可以使用 AddHttpMessageHandler 为客户端工厂配置 SubdirectoryHandler