50

ASP.NET Web APIを使用して一部のHTTP要求をプロキシするアプリケーションを作成していますが、断続的なエラーの原因を特定するのに苦労しています。競合状態のようです...しかし、完全にはわかりません。

ここで詳しく説明する前に、アプリケーションの一般的な通信フローを示します。

  • クライアントはプロキシ1にHTTPリクエストを送信します。
  • プロキシ1はHTTPリクエストの内容をプロキシ2に中継します
  • プロキシ2は、HTTPリクエストの内容をターゲットWebアプリケーションに中継します
  • ターゲットWebアプリはHTTP要求に応答し、応答はプロキシ2にストリーミング(チャンク転送)されます
  • プロキシ2は、プロキシ1に応答を返します。プロキシ1は、元の呼び出し元のクライアントに応答します。

プロキシアプリケーションは、.NET4.5を使用してASP.NETWebAPIRTMで記述されています。リレーを実行するコードは次のようになります。

//Controller entry point.
public HttpResponseMessage Post()
{
    using (var client = new HttpClient())
    {
        var request = BuildRelayHttpRequest(this.Request);

        //HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
        //As it begins to filter in.
        var relayResult = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;

        var returnMessage = BuildResponse(relayResult);
        return returnMessage;
    }
}

private static HttpRequestMessage BuildRelayHttpRequest(HttpRequestMessage incomingRequest)
{
    var requestUri = BuildRequestUri();
    var relayRequest = new HttpRequestMessage(incomingRequest.Method, requestUri);
    if (incomingRequest.Method != HttpMethod.Get && incomingRequest.Content != null)
    {
       relayRequest.Content = incomingRequest.Content;
    }

    //Copies all safe HTTP headers (mainly content) to the relay request
    CopyHeaders(relayRequest, incomingRequest);
    return relayRequest;
}

private static HttpRequestMessage BuildResponse(HttpResponseMessage responseMessage)
{
    var returnMessage = Request.CreateResponse(responseMessage.StatusCode);
    returnMessage.ReasonPhrase = responseMessage.ReasonPhrase;
    returnMessage.Content = CopyContentStream(responseMessage);

    //Copies all safe HTTP headers (mainly content) to the response
    CopyHeaders(returnMessage, responseMessage);
}

private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
    var content = new PushStreamContent(async (stream, context, transport) =>
            await sourceContent.Content.ReadAsStreamAsync()
                            .ContinueWith(t1 => t1.Result.CopyToAsync(stream)
                                .ContinueWith(t2 => stream.Dispose())));
    return content;
}

断続的に発生するエラーは次のとおりです。

非同期操作がまだ保留中に、非同期モジュールまたはハンドラーが完了しました。

このエラーは通常、プロキシアプリケーションへの最初の数回のリクエストで発生し、その後はエラーは再発しません。

Visual Studioは、スローされたときに例外をキャッチすることはありません。ただし、エラーはGlobal.asaxApplication_Errorイベントでキャッチできます。残念ながら、例外にはスタックトレースがありません。

プロキシアプリケーションは、AzureWebロールでホストされます。

犯人を特定する助けをいただければ幸いです。

4

3 に答える 3

68

あなたの問題は微妙なものです:asyncあなたが渡しているラムダPushStreamContentは として解釈されていますasync void(PushStreamContentコンストラクターActionはs のみをパラメーターとして取るため)。したがって、モジュール/ハンドラーの完了とそのasync voidラムダの完了の間に競合状態があります。

PostStreamContentストリームの終了を検出し、それをその終了(モジュール/ハンドラーの完了) として処理するため、ストリームが閉じられた後も実行できるメソッドがTaskないことを確認する必要があります。メソッドは問題ないので、これで修正されるはずです:async voidasync Task

private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
  Func<Stream, Task> copyStreamAsync = async stream =>
  {
    using (stream)
    using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
    {
      await sourceStream.CopyToAsync(stream);
    }
  };
  var content = new PushStreamContent(stream => { var _ = copyStreamAsync(stream); });
  return content;
}

プロキシのスケーリングをもう少し改善したい場合は、すべてのResult呼び出しを取り除くこともお勧めします。

//Controller entry point.
public async Task<HttpResponseMessage> PostAsync()
{
  using (var client = new HttpClient())
  {
    var request = BuildRelayHttpRequest(this.Request);

    //HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
    //As it begins to filter in.
    var relayResult = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

    var returnMessage = BuildResponse(relayResult);
    return returnMessage;
  }
}

以前のコードでは、リクエストごとに (ヘッダーが受信されるまで) 1 つのスレッドがブロックされます。asyncコントローラーレベルまでずっと使用することで、その間スレッドをブロックしません。

于 2013-02-25T15:15:30.750 に答える
4

少し単純なモデルは、実際には HttpContents を直接使用してリレー内で渡すことができるというものです。比較的簡単な方法でコンテンツをバッファリングせずに、リクエストとレスポンスの両方を非同期的に信頼できる方法を示すサンプルをアップロードしました。

http://aspnet.codeplex.com/SourceControl/changeset/view/7ce67a547fd0#Samples/WebApi/RelaySample/ReadMe.txt

必要に応じて接続を再利用できるため、同じ HttpClient インスタンスを再利用することも有益です。

于 2013-02-28T05:48:05.087 に答える