• 【转】.NET HttpClient 当响应标头的内容长度不正确时接受部分响应(Accept partial response when response header has an incorrect ContentLength)


    我正在使用 .NET Core 3.1 开发 ASP.NET Web 应用程序。该应用程序从一个存在错误的外部网络服务器下载 mp3 文件:响应标头中的 Content-Length 报告的字节数高于 mp3 的实际字节数。

    因此,即使 curl 报告传输不完整,mp3 仍以 50294784 字节完全下载,我可以在我尝试过的任何音频播放器中打开它。

    我在我的 Web 应用程序中想要的行为与 curl 相同:忽略不正确的 Content-Length 并下载 mp3,直到服务器关闭传输。

    现在我只是使用 HttpClient 来异步下载 mp3:

    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

  • 相关阅读:
    Flask 学习7. make_response() 自定义响应内容 上海
    Flask 学习5.请求对象Request 上海
    Flask 学习3.设置 HTTP 请求 方法(get/post) 上海
    Flask 学习2.url访问地址(路由配置) 上海
    Flask 学习9. 开启调试模式(debug模式)的2种方法 上海
    modelmapper 简单智能的对象映射工具
    使用jsch 实现ssh tunnel
    jimfs java 内存文件系统实现
    基于commonsdiscovery 开发简单的插件化java系统
    dremio 20 版本可以下载体验了
  • 原文地址:https://www.cnblogs.com/yexiaoyanzi/p/16309697.html
Copyright © 2020-2023  润新知