web-dev-qa-db-ja.com

コンテンツをストリームにコピー中にSystem.Net.Http.HttpRequestExceptionエラー

.Net 4.5.2フレームワークでHttpClientクラスを使用しています。サードパーティのWebサービスに対してPostAsyncを実行しています。この投稿が機能する時間の80%、応答が短くなる時間の20%。この状況では、次の例外が発生します。

System.Net.Http.HttpRequestException:コンテンツをストリームにコピー中にエラーが発生しました。 ---> System.IO.IOException:トランスポート接続からデータを読み取れません:既存の接続が強制的に閉じられましたリモートホスト。 ---> System.Net.Sockets.SocketException:既存の接続は、System.Net.Sockets.NetworkStream.BeginRead(Byte []バッファー、Int32オフセット、Int32サイズ、AsyncCallbackコールバック、オブジェクト状態)でリモートホストによって強制的に閉じられました---内部例外スタックトレースの終わり--- System.Net.FixedSizeReader.StartReading()のSystem.Net.Sockets.NetworkStream.BeginRead(Byte []バッファー、Int32オフセット、Int32サイズ、AsyncCallbackコールバック、オブジェクト状態) System.Net.Security._SslStream.StartFrameHeader(Byte [] buffer、Int32 offset、Int32 count、AsyncProtocolRequest asyncRequest)at System.Net.Security._SslStream.StartReading(Byte [] buffer、Int32 offset、Int32 count、AsyncProtocolRequest asyncRequest) System.Net.Security._SslStream.ProcessRead(Byte [] buffer、Int32 offset、Int32 count、AsyncProtocolRequest asyncRequest)at System.Net.TlsStream.BeginRead(Byte [] buffer、Int32 offset、Int32 size、AsyncCallback asyncCallback、Object asyncState) )System.Net.ConnectStream.BeginReadWithoutValidation(Byte []でSystem.Net.ConnectStream.BeginRead(Byte []バッファ、Int32オフセット、Int32サイズ、AsyncCallbackコールバック、オブジェクト状態)のバッファ、Int32オフセット、Int32サイズ、AsyncCallbackコールバック、オブジェクト状態)System.Net.Http.HttpClientHandler.WebExceptionWrapperStreamでSystem.Net.Http.StreamToStreamCopy.StartRead()での.BeginRead(Byte []バッファー、Int32オフセット、Int32カウント、AsyncCallbackコールバック、オブジェクト状態)

後続の同一の要求は成功します。ビジネスは既に配置されているため、再試行できるリクエストではありません。そのため、私たちは厄介な状況に置かれます。

これは私のコードです:

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秒)かかったことに気付きました。

ご協力ありがとうございました

15
jonho

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ヘッダーを追加する効果があります

    Connection: close 

これよりも

    Connection: keep-alive
20
jonho

REST呼び出しでサーバーに接続する共有HttpClientを使用する場合、同様の問題が発生しました。問題は、クライアントとサーバーの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.
3
Steve Wranovsky

HTTPClient PutAsync()メソッドで同じエラー(コンテンツをストリームにコピー中にエラー)が発生しました:

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

Put-Asyncでは使用できない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);
}
0
kimphamg