【问题标题】:System.Net.Http.HttpRequestException Error while copying content to a streamSystem.Net.Http.HttpRequestException 将内容复制到流时出错
【发布时间】:2016-01-18 22:38:06
【问题描述】:

我在 .NET Framework 4.5.2 中使用 HttpClient 类。

我针对第三方 Web 服务调用 PostAsync。 80% 的时间这篇文章有效,20% 的时间我们的回复被缩短了。在这种情况下,我们会得到以下异常:

System.Net.Http.HttpRequestException:将内容复制到 一个流。 ---> System.IO.IOException: Unable to read data from the 传输连接:现有连接被强行关闭 远程主机。 ---> System.Net.Sockets.SocketException:现有的 连接被远程主机强行关闭 System.Net.Sockets.NetworkStream.BeginRead(字节 [] 缓冲区,Int32 偏移量、Int32 大小、AsyncCallback 回调、对象状态)--- 结束 内部异常堆栈跟踪 --- 在 System.Net.Sockets.NetworkStream.BeginRead(字节 [] 缓冲区,Int32 偏移量,Int32 大小,AsyncCallback 回调,对象状态)在 System.Net.FixedSizeReader.StartReading() 在 System.Net.Security._SslStream.StartFrameHeader(Byte[] 缓冲区,Int32 偏移量,Int32 计数,AsyncProtocolRequest asyncRequest)在 System.Net.Security._SslStream.StartReading(字节 [] 缓冲区,Int32 偏移量,Int32 计数,AsyncProtocolRequest asyncRequest)在 System.Net.Security._SslStream.ProcessRead(字节 [] 缓冲区,Int32 偏移量,Int32 计数,AsyncProtocolRequest asyncRequest)在 System.Net.TlsStream.BeginRead(Byte[] 缓冲区,Int32 偏移量,Int32 大小,AsyncCallback asyncCallback,对象 asyncState)在 System.Net.ConnectStream.BeginReadWithoutValidation(字节 [] 缓冲区, Int32 偏移量、Int32 大小、AsyncCallback 回调、对象状态)在 System.Net.ConnectStream.BeginRead(Byte[] 缓冲区,Int32 偏移量,Int32 大小,AsyncCallback 回调,对象状态)在 System.Net.Http.HttpClientHandler.WebExceptionWrapperStream.BeginRead(字节[] 缓冲区、Int32 偏移量、Int32 计数、AsyncCallback 回调、对象 状态)在 System.Net.Http.StreamToStreamCopy.StartRead()

随后的相同请求成功。

我们无法重试此请求,因为已采取业务操作。所以这让我们陷入了尴尬的境地。

这是我的代码:

using (var httpClient = new HttpClient())
{
    httpClient.DefaultRequestHeaders.Authorization = authorizationHeader;
    HttpContent httpContent = new StringContent(someXml);

    //Exception occurs on next line...
    var response = await httpClient.PostAsync("https://thirdpartyendpoint", httpContent);
    var responseXml = await response.Content.ReadAsStringAsync();  
    //convert to Dto              
}

第三方服务已成功将记录保存到他们的数据库中,并且在他们的末尾没有看到任何明显的异常。他们确实注意到,失败的请求通常比成功的请求花费更长的时间(大约 18-30 秒)写入数据库。

我能做些什么来更好地处理这个问题?

【问题讨论】:

  • 他们的数据库中似乎存在阻塞操作或错误索引。尝试分析数据库查询或监控数据库锁。
  • 嗨 Roman,尽管有额外的延迟,但他们并没有在代码中引发异常,而是完成了响应。我们只是没有收到它。
  • 他们会检查 HTTP 连接超时。

标签: c# asp.net .net asp.net-web-api


【解决方案1】:

我们通过 2 处代码更改解决了这个问题:

  1. 处理 httpResponseMessage 并使用简单的 DTO

    using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage))
    {
        return await CreateDto(httpResponseMessage);
    }
    
  2. 将HTTP版本降级到v1.0

    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(url))
    {
        Version = HttpVersion.Version10,
        Content = httpContent
    };
    
    await client.SendAsync(httpRequestMessage);
    

有添加这个Http header的效果

Connection: close 

而不是这个

Connection: keep-alive

【讨论】:

  • 关于如何将 HttpVersion 应用于 httpClient 获取请求的任何想法?
  • var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(url)) { Version = HttpVersion.Version10, Content = httpContent };等待客户端.SendAsync(httpRequestMessage);应该这样做
  • 使用 DTO 解决了我的问题。我怀疑它会有所帮助。谢谢!
  • 只需将 DefaultRequestHeaders.ConnectionClose 更改为 true 即可为我工作 :)
  • 降级到 HTTP v1.0 对我有用。我发现在发布到 Windows Server 2008 R2 而不是 Windows Server 2012 时需要这样做,知道为什么吗?
【解决方案2】:

在使用共享 HttpClient 连接到服务器以进行 REST 调用时,我遇到了类似的问题。问题最终是客户端和服务器上的 KeepAlive 超时不匹配。客户端超时由 ServicePointManager 上的 MaxServicePointIdleTime 设置设置,默认为 100 秒。服务器端空闲超时在我们的服务器中设置为较短的值。

与客户端相比,服务器上的超时时间更短导致服务器在客户端尝试连接时偶尔关闭连接。这导致了报告的异常。

请注意,我最终发现了问题,因为我在相同的条件下也收到了这个异常:

System.Net.WebException: The underlying connection was closed: A connection that was expected to be kept alive was closed by the server.

【讨论】:

  • 这听起来很有趣。你能说一下你做了什么来设置服务器空闲超时吗?对于客户端,我猜你使用了ServicePointManager.FindServicePoint(...)
  • @user2173353,我们有一个客户 HttpServer,我们可以直接在套接字上设置 KeepAlive。我不确定在其他情况下如何设置。
【解决方案3】:

我在使用 HTTPClient PutAsync() 方法时遇到了同样的错误(将内容复制到流时出错):

using (StreamContent content = new StreamContent(stream))
{
    HttpResponseMessage response = await client.PutAsync(url, content))
}

您需要指定 PutAsync 中不可用的 HttpCompletionOption.ResponseHeadersRead 标志,因此我切换到 SendAsync:

using (StreamContent content = new StreamContent(stream))
{
    var httpRequest = new HttpRequestMessage(HttpMethod.Put, url);
    httpRequest.Content = content;

    HttpResponseMessage response = await client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-24
  • 2020-04-10
  • 1970-01-01
  • 2018-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多