HttpClient の使用に関するガイドライン

System.Net.Http.HttpClient クラスは、URI で識別されるリソースに HTTP 要求を送信し、そのリソースから HTTP 応答を受信します。 HttpClient インスタンスは、そのインスタンスによって実行されるすべての要求に適用される設定のコレクションであり、各インスタンスでは独自の接続プールが使用されるため、要求は相互に分離されます。 .NET Core 2.1 以降の SocketsHttpHandler クラスによって実装が提供され、すべてのプラットフォームで動作の一貫性が保たれます。

DNS の動作

HttpClient では、接続が作成されるときにのみ DNS のエントリが解決されます。 DNS サーバーによって指定されている有効期限 (TTL) の期間は追跡されません。 一部のコンテナーのシナリオで発生する可能性があるような、DNS エントリが定期的に変更される場合、クライアントではこれらの更新は適用されません。 この問題を解決するには、接続が置き換えられるときに DNS 検索回数 が必要になるように、PooledConnectionLifetime プロパティを設定することで、接続の有効期間を制限できます。 次に例を示します。

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(15) // Recreate every 15 minutes
};
var sharedClient = new HttpClient(handler);

上記の HttpClient は、接続を 15 分間再利用するように構成されています。 PooledConnectionLifetime で指定された期間が経過すると、接続は閉じられ、新しい接続が作成されます。

プールされた接続

HttpClient の接続プールは、その基になる SocketsHttpHandler にリンクされます。 HttpClient インスタンスが破棄されると、プール内のすべての既存の接続が破棄されます。 後で同じサーバーに要求を送信すると、新しい接続を再作成する必要があります。 その結果、不要な接続の作成に対するパフォーマンスの低下が発生します。 そのうえ、TCP ポートは接続が閉じられても、すぐには解放されません。 (詳しくは、RFC 9293 の TCP TIME-WAIT をご覧ください)。要求のレートが高い場合は、オペレーティング システムで使用できるポートの制限が使い果たされる場合があります。 ポート枯渇の問題を回避するには、できる限り多く HTTP 要求の HttpClient インスタンスを再利用することをお勧めします

有効期間管理の観点から推奨される HttpClient の使用方法を要約すると、PooledConnectionLifetime (.NET Core および .NET 5 以降) が設定された "有効期間の長い" クライアントを使うか、IHttpClientFactory で作成された "有効期間の短い" クライアントを使う必要があります。

  • .NET Core と .NET 5 以降の場合:

    • static または "シングルトン" の HttpClient インスタンスと共に、予想される DNS の変更に応じた必要な間隔 (2 分など) が設定された PooledConnectionLifetime を使用します。 これにより、IHttpClientFactory のオーバーヘッドを増やすことなく、ポートの枯渇と DNS の変更の両方の問題が解決されます。 ハンドラーをモックできるようにする必要がある場合は、個別に登録できます。

    ヒント

    限られた数の HttpClient インスタンスしか使用しないのであれば、それも許容できる方策です。 重要なことは、HttpClient インスタンスにはそれぞれ接続プールが含まれるため、要求が発生するたびに HttpClient インスタンスが作成されたり破棄されたりしないことです。 複数のプロキシを使用するシナリオや、Cookie 処理を完全に無効にせずに Cookie コンテナーを分離するには、複数のインスタンスを使用する必要があります。

    • IHttpClientFactory を使うと、異なるユース ケース用に、複数の異なる構成のクライアントを使用できます。 ただし、ファクトリによって作成されたクライアントは有効期間が短く、クライアントが作成されると、ファクトリで制御できなくなることに注意してください。

      ファクトリが新しい HttpClient インスタンスを作成するとき、ファクトリが HttpMessageHandler インスタンスをプールし、その有効期間が切れていない場合は、プールからハンドラーを再利用できます。 この再利用により、ソケットの枯渇の問題が回避されます。

      IHttpClientFactory によって提供される構成可能性が必要な場合は、型指定されたクライアントのアプローチを使用することをお勧めします。

  • .NET Framework では、IHttpClientFactory を使って HttpClient インスタンスを管理します。 ファクトリを使用せず、要求ごとに新しいクライアント インスタンスを作成する場合は、使用できるポートを使い果たしてしまうことがあります。

    ヒント

    アプリで Cookie が必要な場合は、Cookie の自動処理を無効にするか、IHttpClientFactory を使わないようにすることを検討してください。 HttpMessageHandler インスタンスのプールを使用すると、CookieContainer オブジェクトが共有されるようになります。 予期せぬ CookieContainer オブジェクト共有があると、多くの場合、コードは不適切なものとなります。

IHttpClientFactory を使用した HttpClient の有効期間管理について詳しくは、IHttpClientFactory ガイドラインをご覧ください。

静的クライアントを使用した回復性

次のパターンを使用すると、static または のシングルトン クライアントが、任意の数の回復性パイプラインを使用するように構成できます。

using System;
using System.Net.Http;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Http.Resilience;
using Polly;

var retryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRetry(new HttpRetryStrategyOptions
    {
        BackoffType = DelayBackoffType.Exponential,
        MaxRetryAttempts = 3
    })
    .Build();

var socketHandler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(15)
};
var resilienceHandler = new ResilienceHandler(retryPipeline)
{
    InnerHandler = socketHandler,
};

var httpClient = new HttpClient(resilienceHandler);

上記のコードでは次の操作が行われます。

  • Microsoft.Extensions.Http.Resilience NuGet パッケージに依存します。
  • 再試行のたびに遅延間隔をエクスポネンシャル バックオフする再試行パイプラインで構成された、一時的な HTTP エラー ハンドラーを指定します。
  • socketHandler に対し、プールされた接続の有効期間を 15 分に定義します。
  • 再試行ロジックにより socketHandlerresilienceHandler に渡します。
  • resilienceHandler が指定された HttpClient のインスタンスを作成します。

重要

Microsoft.Extensions.Http.Resilience ライブラリは現在、試験段階のマークが付けられており、将来変更される可能性があります。

関連項目