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

作者:James Newton-King

本文档讨论了在 .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 httpHandler = new HttpClientHandler();
// Return `true` to allow certificates that are untrusted/invalid
httpHandler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

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

警告

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

使用 .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 不支持 macOS 和更早的 Windows 版本(如 Windows 7)上的带有 TLS 的 HTTP/2。 默认情况下,ASP.NET Core gRPC 模板和示例使用 TLS。 尝试启动 gRPC 服务器时,你将看到以下错误消息:

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

若要解决此问题,请将 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 调用时,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 服务器结合使用

HTTP/3 支持在 .NET 6 中以预览版提供,需要通过项目文件中的一个配置标志启用:

<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

警告

若要将 ASP.NET Core gRPC 用于 Azure 应用服务或 IIS,则该 gRPC 具有额外的要求。 有关可以在何处使用 gRPC 的详细信息,请参阅 .NET 支持的平台上的 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 httpHandler = new HttpClientHandler();
// Return `true` to allow certificates that are untrusted/invalid
httpHandler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

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

警告

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

使用 .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 不支持 macOS 和更早的 Windows 版本(如 Windows 7)上的带有 TLS 的 HTTP/2。 默认情况下,ASP.NET Core gRPC 模板和示例使用 TLS。 尝试启动 gRPC 服务器时,你将看到以下错误消息:

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

若要解决此问题,请将 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 调用时,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

警告

若要将 ASP.NET Core gRPC 用于 Azure 应用服务或 IIS,则该 gRPC 具有额外的要求。 有关可以在何处使用 gRPC 的详细信息,请参阅 .NET 支持的平台上的 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 httpHandler = new HttpClientHandler();
// Return `true` to allow certificates that are untrusted/invalid
httpHandler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

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

警告

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

使用 .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 不支持 macOS 和更早的 Windows 版本(如 Windows 7)上的带有 TLS 的 HTTP/2。 默认情况下,ASP.NET Core gRPC 模板和示例使用 TLS。 尝试启动 gRPC 服务器时,你将看到以下错误消息:

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

若要解决此问题,请将 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 调用时,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

警告

若要将 ASP.NET Core gRPC 用于 Azure 应用服务或 IIS,则该 gRPC 具有额外的要求。 有关可以在何处使用 gRPC 的详细信息,请参阅 .NET 支持的平台上的 gRPC