GRPC - HTTP/2 服务器在连接上发送了无效数据。从 .netcore 3.1 升级到 net5 或 net6 时出现 HTTP/2 错误代码“PROTOCOL_ERROR”

Bar*_*osz 2 c# .net-core grpc .net-5 .net-6.0

我有一个 .netcore3.1 测试项目,它调用 GRPC 服务。
\n该服务只能通过 HTTP 获得。我现在无法更改服务。

\n

当我将项目更新到 .net5-windows 或 .net6-windows(实际上只是 csproj 更改)时,调用失败并出现以下错误:

\n
\n

Grpc.Core.RpcException : Status(StatusCode="Unavailable", Detail="启动 gRPC 调用时出错。HttpRequestException: 发送请求时发生错误。IOException: 请求已中止。Http2ConnectionException: HTTP/2 服务器发送了无效数据连接。HTTP/2 错误代码 \'PROTOCOL_ERROR\' (0x1)。", DebugException="System.Net.Http.HttpRequestException: 发送请求时发生错误。\n---> System.IO.IOException:请求已中止。\n---> System.Net.Http.Http2ConnectionException: HTTP/2 服务器在连接上发送了无效数据。HTTP/2 错误代码 \'PROTOCOL_ERROR\' (0x1)。\nat System.Net .Http.Http2Connection.ThrowProtocolError(Http2ProtocolErrorCode errorCode)\nat System.Net.Http.Http2Connection.ReadFrameAsync(Boolean initialFrame)\nat System.Net.Http.Http2Connection.ProcessIncomingFramesAsync()\n--- 内部异常堆栈跟踪结束 - --\nat System.Net.Http.Http2Connection.ThrowRequestAborted(Exception innerException)\nat System.Net.Http.Http2Connection.Http2Stream.CheckResponseBodyState()\nat System.Net.Http.Http2Connection.Http2Stream.TryEnsureHeaders()\nat System .Net.Http.Http2Connection.Http2Stream.ReadResponseHeadersAsync(CancellationToken CancellationToken)\nat System.Net.Http.Http2Connection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken CancellationToken)\n--- 内部异常堆栈跟踪结束 ---\ nat System.Net.Http.Http2Connection.SendAsync(HttpRequestMessage 请求,布尔异步,CancellationToken CancellationToken)\nat System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage 请求,布尔异步,布尔 doRequestAuth,CancellationToken CancellationToken)\nat System.Net。 Http.RedirectHandler.SendAsync(HttpRequestMessage 请求、布尔异步、CancellationToken CancellationToken)\nat Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore(HttpRequestMessage 请求、CancellationToken CancellationToken)\nat System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage请求,HttpCompletionOption finishOption,CancellationTokenSource cts,布尔disposeCts,CancellationTokenSourceendingRequestsCts,CancellationToken原始CancellationToken)\ nat Grpc.Net.Client.Internal.GrpcCall 2.RunCall(HttpRequestMessage request, Nullable1超时)“)\ nat ..

\n
\n

我查看了几个SO问题(例如带有未加密的HTTP2连接的.Net Core 3.1 gRPC client and Grpc.Core.RpcException: 'Status (StatusCode = "Unavailable", Detail = "Error running gRPC call ) 以及 \'调用不安全服务的故障排除指南https://learn.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-5.0#call-insecure-grpc-services-with-net-core-client然而它们没有帮助。

\n

在初始化客户端之前,我尝试了一些设置 AppContext 的选项(即使它们应该申请 netcore3.1),但同样没有帮助:

\n
  AppContext.SetSwitch(\xe2\x80\x9cSystem.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport\xe2\x80\x9d, true);\n  AppContext.SetSwitch(\xe2\x80\x9cSystem.Net.Http.SocketsHttpHandler.Http2Support\xe2\x80\x9d, true);\n
Run Code Online (Sandbox Code Playgroud)\n

通道初始化如下:

\n
 var baseUri = new Uri(baseUrl);\n        HttpMessageHandler httpMessageHandler = new HttpClientHandler();\n        if (baseUri.AbsolutePath != "/")\n            httpMessageHandler = new SubDirectoryHandler(httpMessageHandler, baseUri.AbsolutePath);\n        var handler = new GrpcWebHandler(GrpcWebMode.GrpcWebText, httpMessageHandler);\n        _channel = GrpcChannel.ForAddress(baseUrl, new GrpcChannelOptions { HttpClient = new HttpClient(handler) });\n
Run Code Online (Sandbox Code Playgroud)\n

在发送请求之前检查 GRPC 通道显示请求版本为 1.1(无论 AppContext 设置和 TargetFramework 如何):

\n

在此输入图像描述

\n

GRPC 客户端库是:

\n
\n

Grpc.Net.Client v.2.42.0
\nGrpc.Net.Client.Web v.2.42.0

\n
\n

发送请求时,服务端将在输出中记录以下内容:

\n
\n

抛出异常: Microsoft.AspNetCore.Server.Kestrel.Core.dll 中的 \'Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException\'
\n抛出异常: \'Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException\' 中System.Private.CoreLib.dll\n\'TradingOrchestrationService.exe\'(CoreCLR:clrhost):已加载\'C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App\\6.0.5\ \Microsoft.AspNetCore.WebUtilities.dll\'。
\n抛出异常: Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll 中的 \'System.Net.Sockets.SocketException\'
\n由于线程退出或应用程序请求,I/O 操作已中止。

\n
\n

我想强调的关键是工作和非工作行为之间的唯一区别是将入口程序集中的 TargetFramework 版本从 3.1 提高到 5 或 6。

\n

Bar*_*osz 9

为了完整性和将来的参考,我将从 JamesNK 获得的回复发布在 GitHub 上https://github.com/dotnet/runtime/issues/71906

使用 HTTP 时,客户端和服务器必须使用相同的协议。没有 https 就没有协商。

Kestrel 配置为使用什么协议?HTTP/1.1 还是 HTTP/2?如果您没有明确地将其配置为仅 HTTP/2,那么它可能会是 HTTP/1.1。

GrpcWebHandler 有一个 HttpVersion 属性,您可以设置该属性来显式指定您想要的 HTTP 版本。您可以尝试将其设置为新版本(1, 1)。

设置此属性是否有其他含义?
当我们将服务器迁移到使用 HTTP/2 时,我想它必须被删除?

是的

您发生了什么:在 .NET Core 3.x 中,gRPC 客户端将 gRPC 请求配置为 HTTP/2,但由于您没有使用 TLS,该请求被悄悄降级为 HTTP/1.1

该问题已在 .NET 5+ 中得到修复,但您依赖的是损坏的行为,因此该修复让您崩溃了。

客户端现在通过 http 发送 HTTP/2,但您的服务器配置为 HTTP/1.1。这会产生协议错误。GrpcWebHandler.HttpVersion 为您提供了一种覆盖 HTTP 版本并准确指定您想要的内容的方法(因为 gRPC-Web 适用于这两个 HTTP 版本)。