【转】.NET HttpClient - 当响应标头的内容长度不正确时接受部分响应(Accept partial response when response header has an incorrect Content-Length)
我正在使用 .NET Core 3.1 开发 ASP.NET Web 应用程序。该应用程序从一个存在错误的外部网络服务器下载 mp3 文件:响应标头中的 Content-Length 报告的字节数高于 mp3 的实际字节数。
因此,即使 curl 报告传输不完整,mp3 仍以 50294784 字节完全下载,我可以在我尝试过的任何音频播放器中打开它。
我在我的 Web 应用程序中想要的行为与 curl 相同:忽略不正确的 Content-Length 并下载 mp3,直到服务器关闭传输。
现在我只是使用 HttpClient 来异步下载 mp3:
1 2 | using ( var response = await httpClient.GetAsync( downloadableMp3.Uri, HttpCompletionOption.ResponseContentRead ) ) using ( var streamToReadFrom = await response.Content.ReadAsStreamAsync() ) |
但是,与 curl 不同的是,当传输过早关闭时,传输会作为一个整体中止:
Task <SchedulerTaskWrapper FAILED System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: The response ended prematurely. at System.Net.Http.HttpConnection.FillAsync() at System.Net.Http.HttpConnection.CopyToContentLengthAsync(Stream destination, UInt64 length, Int32 bufferSize, CancellationToken cancellationToken) at System.Net.Http.HttpConnection.ContentLengthReadStream.CompleteCopyToAsync(Task copyTask, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionResponseContent.SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken) at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer) --- End of inner exception stack trace --- at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer) at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
解决方案:
如果您查看 dotnet 运行时 repo 中的方法SendAsyncCore,您会看到相当大的代码实现了发送请求和处理响应的核心功能。如果服务器发送 content-length 标头,则此方法在内部创建ContentLengthReadStream。此流需要固定数量的字节,并且会一直读取,直到达到预期数量。如果 content-length 大于实际字节数,则ContentLengthReadStream会抛出异常消息The response ended prematurely
。
由于所有这些方法都非常僵化和内部化,因此没有扩展或更改此功能的空间。但是有一个解决方法。您可以手动将流读入缓冲区,直到抛出异常。流的正常终止条件是 Read 方法返回零字节。如果内容长度正确,则还应包括此条件。
1 using var resp = await httpClient.GetAsync("http://example.com/test.mp3", HttpCompletionOption.ResponseHeadersRead); 2 using var contentStream = await resp.Content.ReadAsStreamAsync(); 3 4 var bufferSize = 2048; 5 var buffer = new byte[bufferSize]; 6 var result = new List<byte>(); 7 8 try 9 { 10 var readBytes = 0; 11 while ((readBytes = contentStream.Read(buffer)) != 0) 12 { 13 for (int i = 0; i < readBytes; i++) 14 { 15 result.Add(buffer[i]); 16 } 17 } 18 } 19 catch (IOException ex) 20 { 21 if (!ex.Message.StartsWith("The response ended prematurely")) 22 { 23 throw; 24 } 25 }
另请注意,您不应该HttpCompletionOption.ResponseContentRead
在这种情况下使用,因为如果您调用GetAsync
方法,它会尝试立即读取内容。由于我们要稍后阅读内容,因此应将其更改为 HttpCompletionOption.ResponseHeadersRead。这意味着GetAsync
在读取标头时完成操作(尚未读取内容)。
解决2:
经过调查,默认情况下 HttpClient 等待响应头 + 为所有方法准备好的内容,例如。PostAsync、SendAsync、GetAsync。所以如果响应没有/无效的内容长度。你会得到这个错误。看到这个。所以建议使用 SendAsync(xxx, HttpCompletionOption.ResponseHeadersRead)
1 using (var req = new HttpRequestMessage(HttpMethod.Post, url)) 2 { 3 var body = File.ReadAllText("body.txt").Trim(); 4 5 req.Content = new StringContent(body, Encoding.UTF8, "application/json-patch+json"); 6 7 // the follow code throw exception 8 // System.IO.IOException: The response ended prematurely 9 // since 'HttpCompletionOption.ResponseContentRead' is the default behavior 10 // var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseContentRead); 11 12 var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead); 13 14 response.EnsureSuccessStatusCode(); 15 }
转:https://stackoverflow.com/questions/59590012/net-httpclient-accept-partial-response-when-response-header-has-an-incorrect
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!