web-dev-qa-db-ja.com

Web ApiでHttpContext.Currentを使用すると、非同期のため危険です

私の質問は、これに少し関連しています: WebApiに相当するHttpContext.Items with Dependency Injection

Ninjectを使用してWebApiエリアでHttpContext.Currentを使用してクラスを注入します。

私の懸念は、WebApi(everything?)は非同期であるため、これは非常に危険になる可能性があることです。

これらの点で間違っている場合は私を修正してください、これは私がこれまでに調査したものです:

  1. HttpContext.Currentは、スレッドによって現在のコンテキストを取得します(実装を直接調べました)。

  2. 非同期タスク内でHttpContext.Currentを使用することはできません。別のスレッドで実行できるためです。

  3. WebApiは、メソッドTask<HttpResponseMessage> ExecuteAsync =>すべてのリクエストが非同期=>アクションメソッド内でHttpContext.Currentを使用できないIHttpControllerを使用します。偶然にも同じスレッドでより多くのリクエストが実行される可能性もあります。

  4. コンストラクターにインジェクトされたものを使用してコントローラーを作成するには、IHttpControllerActivatorをsync method IHttpController Createと共に使用します。これは、ninjectがすべての依存関係を持つControllerを作成する場所です。


  • これら4つの点すべてが正しい場合、アクションメソッドまたはその下のレイヤー内でHttpContext.Currentを使用することは非常に危険であり、予期しない結果になる可能性があります。私はSOこれを正確に示唆する多くの受け入れられた答えを見ました。これはしばらくは機能しますが、負荷がかかると失敗します。

  • ただし、DIを使用してコントローラーとその依存関係を作成する場合は、1つの独立したスレッドで実行されるため、OKです。 コンストラクターでHttpContextから値を取得できましたが、安全ですか?。 IISからのすべてのスレッドが消費される可能性がある重い負荷の下で問題を引き起こす可能性があるため、各コントローラーがすべての要求に対して単一のスレッドで作成されるのだろうか。

ちょうどHttpContextのものを注入したい理由を説明するために:

  • 1つの解決策は、コントローラーアクションメソッドで要求を取得し、コードの深部で使用されるまで、必要な値をすべてのレイヤーにパラメーターとして渡すことです。
  • 必要なソリューション:間のすべてのレイヤーはこれに影響されず、挿入されたリクエストをコードのどこかで使用できます(URLに依存するConfigurationProviderなど)

    このテーマは非常に複雑であると思われるため、私が完全に間違っているか、私の提案が正しい場合は、ご意見をお聞かせください。事前にThx!

58
Lukas K

HttpContext.Currentは、スレッドによって現在のコンテキストを取得します(実装を直接調べました)。

HttpContextがスレッドに適用されると言う方が正しいでしょう。または、スレッドがHttpContextを「入力」します。

非同期タスク内でHttpContext.Currentを使用することはできません。別のスレッドで実行できるためです。

どういたしまして; async/awaitのデフォルトの動作は任意のスレッドで再開されますが、そのスレッドはasyncメソッドを再開する前にリクエストコンテキストに入ります。


これの鍵はSynchronizationContextです。よくわからない場合は、 この件に関するMSDN記事 があります。 SynchronizationContextは、プラットフォームの「コンテキスト」を定義します。一般的なものは、UIコンテキスト(WPF、WinPhone、WinFormsなど)、スレッドプールコンテキスト、およびASP.NET要求コンテキストです。

ASP.NET要求コンテキストは、HttpContext.Currentだけでなく、カルチャやセキュリティなどの他のいくつかのものを管理します。 UIコンテキストはすべて単一のスレッド(theUIスレッド)と密接に関連付けられていますが、ASP.NET要求コンテキストは特定のスレッドに関連付けられていません。ただし、リクエストコンテキストでは一度に1つのスレッドのみを許可します

ソリューションのその他の部分は、asyncおよびawaitの仕組みです。私のブログには asyncのイントロがあり、その動作を説明しています です。要約すると、awaitはデフォルトで現在のコンテキスト(nullでない限りSynchronizationContext.Current)をキャプチャし、そのコンテキストを使用してasyncメソッドを再開します。したがって、awaitは自動的にASP.NET SynchronizationContextをキャプチャし、そのリクエストコンテキスト内でasyncメソッドを再開します(したがって、文化、セキュリティ、およびHttpContext.Currentを保持します)。

awaitConfigureAwait(false)の場合、awaitにコンテキストをnotキャプチャしないように明示的に伝えています。

ASP.NETは、SynchronizationContext/asyncで正常に動作するようにawaitを変更する必要があったことに注意してください。アプリケーションが.NET 4.5に対してコンパイルされていることを確認する必要があります web.configで明示的に4.5もターゲットに設定 ;これは新しいASP.NET 4.5プロジェクトのデフォルトですが、既存のプロジェクトをASP.NET 4.0以前からアップグレードした場合は明示的に設定する必要があります。

.NET 4.5に対してアプリケーションを実行し、SynchronizationContext.Currentを監視することにより、これらの設定が正しいことを確認できます。 AspNetSynchronizationContextである場合、あなたは大丈夫です。 LegacyAspNetSynchronizationContextの場合、設定は間違っています。

設定が正しい限り(そしてASP.NET 4.5 AspNetSynchronizationContextを使用している限り)、awaitの後に心配することなくHttpContext.Currentを安全に使用できます。

97
Stephen Cleary

私は、非同期/待機方法を使用しているWeb APIを使用しています。

また使用して

1) HttpContext.Current.Server.MapPath
2) System.Web.HttpContext.Current.Request.ServerVariables

これはかなりの時間正常に機能していましたが、コード変更なしで突然壊れました。

以前の古いバージョンに戻すことで多くの時間を費やしましたが、見つからないキーが問題の原因であることがわかりました。

< httpRuntime targetFramework="4.5.2"  /> under system.web

私は専門家ではありません。ただし、Web構成にキーを追加して、[〜#〜] go [〜#〜]を指定することをお勧めします。

4
Selva Ramaswamy

この問題を正確に説明する非常に良い記事を見つけました: http://byterot.blogspot.cz/2012/04/aspnet-web-api-series-part-3-async-deep.html?m=1

著者は、WebApiフレームワークでExecuteAsyncメソッドがどのように呼び出されるかを深く調査し、この結論に達しました。

ASP.NET Web APIアクション(およびすべてのパイプラインメソッド)は、TaskまたはTask <T>を返す場合にのみ非同期的に呼び出されます。これは明白に聞こえるかもしれませんが、Asyncサフィックスを持つパイプラインメソッドは、独自のスレッドで実行されません。ブランケット非同期を使用することは、誤った名称です。 [更新:ASP.NETチームは、非同期を使用してTaskを返し、非同期で実行できますが、そうする必要がないメソッドを示すことを実際に確認しました]

この記事から理解したことは、Actionメソッドは同期的に呼び出されるということですが、それは呼び出し側の決定です。

この目的のために、次のような小さなテストアプリを作成しました。

_public class ValuesController : ApiController
{
    public object Get(string clientId, string specialValue)
    {
        HttpRequest staticContext = HttpContext.Current.Request;
        string staticUrl = staticContext.Url.ToString();

        HttpRequestMessage dynamicContext = Request;
        string dynamicUrl = dynamicContext.RequestUri.ToString();

        return new {one = staticUrl, two = dynamicUrl};
    }
}
_

および_async Task<object>_を返す1つの非同期バージョン

私はjqueryで少しDOS攻撃を試みましたが、await Task.Delay(1).ConfigureAwait(false);を使用するまで問題を特定できませんでした。

私が記事から取ったのは、問題は非常に複雑であり、非同期アクションメソッドを使用するとスレッドの切り替えが発生する可能性があるため、明確に[〜#〜] not [〜#〜]アクションメソッドから呼び出されるコードの任意の場所でHttpContext.Currentを使用することをお勧めします。ただし、コントローラーは同期的に作成されるため、コンストラクターでHttpContext.Currentを使用し、依存関係の注入でも問題ありません。

誰かがこの問題について別の説明をしているとき、この問題は非常に複雑であり、私はまだ100%確信していないので、私を修正してください。

diclaimer:

  • とりあえずHttpContext.Currentがおそらく動作しない、IISを使用しないセルフホストWeb-Apiの問題は今のところ無視します。現在、IISに依存しています。
2
Lukas K