对 .NET 上的 gRPC 进行故障排除
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .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 服务还需要满足一些额外要求:
- .NET 5 或更高版本要求使用 Grpc.Net.Client 版本 2.32.0 或更高版本。
- .NET Core 3.x 要求进行额外配置。 该应用必须将
System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport
开关设置为true
。 有关详细信息,请参阅 Asp.Net Core 3.x:使用 .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 文件和工具。 必须包括:
- 要在
<Protobuf>
项目组中使用的.proto
文件。 导入的.proto
文件必须由项目引用。 - 对 gRPC 工具包 Grpc.Tools 的包引用。
有关生成 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 指令或程序集引用?)
可以通过以下方式解决此问题:
- 创建新的 .NET Core 类库项目。
- 在新项目中,添加引用以从
.proto
文件启用 C# 代码生成:- 添加以下包引用:
- 将
.proto
文件添加到<Protobuf>
项目组。
- 在 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 应用还需执行其他步骤。
提前生成代码
一种解决方案可提前生成代码。
- 将
.proto
文件和Grpc.Tools
包引用移动到新项目。 - 将项目作为 NuGet 包发布,然后将其上传到 NuGet 源。
- 更新应用以引用 NuGet 包。
通过上述步骤,应用不再需要 Grpc.Tools
来构建,因为代码可提前生成。
自定义 Grpc.Tools
本机二进制文件
Grpc.Tools
支持使用自定义本机二进制文件。 此功能允许 gRPC 工具在其捆绑的本机二进制文件不支持的环境中运行。
生成或获取 protoc
和 grpc_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.
# 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 构建集成文档。
来自 HttpClient.Timeout
的 gRPC 调用超时
默认情况下,HttpClient 配置为 100 秒超时。 当将 GrpcChannel
配置为使用 HttpClient
时,如果长时间运行的 gRPC 流式处理调用未在超时限制内完成,则系统会取消这些调用。
System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
可通过几种方法来修复此错误。 第一个是将 HttpClient.Timeout 配置为更大的值。 Timeout.InfiniteTimeSpan 禁用超时:
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
或者,请改为设置 GrpcChannel.HttpHandler
,而避免创建 HttpClient
:
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);
本文档讨论了在 .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 文件和工具。 必须包括:
- 要在
<Protobuf>
项目组中使用的.proto
文件。 导入的.proto
文件必须由项目引用。 - 对 gRPC 工具包 Grpc.Tools 的包引用。
有关生成 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 指令或程序集引用?)
可以通过以下方式解决此问题:
- 创建新的 .NET Core 类库项目。
- 在新项目中,添加引用以从
.proto
文件启用 C# 代码生成:- 添加以下包引用:
- 将
.proto
文件添加到<Protobuf>
项目组。
- 在 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 应用还需执行其他步骤。
提前生成代码
一种解决方案可提前生成代码。
- 将
.proto
文件和Grpc.Tools
包引用移动到新项目。 - 将项目作为 NuGet 包发布,然后将其上传到 NuGet 源。
- 更新应用以引用 NuGet 包。
通过上述步骤,应用不再需要 Grpc.Tools
来构建,因为代码可提前生成。
自定义 Grpc.Tools
本机二进制文件
Grpc.Tools
支持使用自定义本机二进制文件。 此功能允许 gRPC 工具在其捆绑的本机二进制文件不支持的环境中运行。
生成或获取 protoc
和 grpc_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.
# 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 构建集成文档。
来自 HttpClient.Timeout
的 gRPC 调用超时
默认情况下,HttpClient 配置为 100 秒超时。 当将 GrpcChannel
配置为使用 HttpClient
时,如果长时间运行的 gRPC 流式处理调用未在超时限制内完成,则系统会取消这些调用。
System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
可通过几种方法来修复此错误。 第一个是将 HttpClient.Timeout 配置为更大的值。 Timeout.InfiniteTimeSpan 禁用超时:
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
或者,请改为设置 GrpcChannel.HttpHandler
,而避免创建 HttpClient
:
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);
本文档讨论了在 .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 文件和工具。 必须包括:
- 要在
<Protobuf>
项目组中使用的.proto
文件。 导入的.proto
文件必须由项目引用。 - 对 gRPC 工具包 Grpc.Tools 的包引用。
有关生成 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 指令或程序集引用?)
可以通过以下方式解决此问题:
- 创建新的 .NET Core 类库项目。
- 在新项目中,添加引用以从
.proto
文件启用 C# 代码生成:- 添加以下包引用:
- 将
.proto
文件添加到<Protobuf>
项目组。
- 在 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
。
来自 HttpClient.Timeout
的 gRPC 调用超时
默认情况下,HttpClient 配置为 100 秒超时。 当将 GrpcChannel
配置为使用 HttpClient
时,如果长时间运行的 gRPC 流式处理调用未在超时限制内完成,则系统会取消这些调用。
System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
可通过几种方法来修复此错误。 第一个是将 HttpClient.Timeout 配置为更大的值。 Timeout.InfiniteTimeSpan 禁用超时:
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
或者,请改为设置 GrpcChannel.HttpHandler
,而避免创建 HttpClient
:
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);
本文档讨论了在 .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 文件和工具。 必须包括:
- 要在
<Protobuf>
项目组中使用的.proto
文件。 导入的.proto
文件必须由项目引用。 - 对 gRPC 工具包 Grpc.Tools 的包引用。
有关生成 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 指令或程序集引用?)
可以通过以下方式解决此问题:
- 创建新的 .NET Core 类库项目。
- 在新项目中,添加引用以从
.proto
文件启用 C# 代码生成:- 添加以下包引用:
- 将
.proto
文件添加到<Protobuf>
项目组。
- 在 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
。
来自 HttpClient.Timeout
的 gRPC 调用超时
默认情况下,HttpClient 配置为 100 秒超时。 当将 GrpcChannel
配置为使用 HttpClient
时,如果长时间运行的 gRPC 流式处理调用未在超时限制内完成,则系统会取消这些调用。
System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
可通过几种方法来修复此错误。 第一个是将 HttpClient.Timeout 配置为更大的值。 Timeout.InfiniteTimeSpan 禁用超时:
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
或者,请改为设置 GrpcChannel.HttpHandler
,而避免创建 HttpClient
:
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);