IHttpClientFactory を使用して回復性のある HTTP 要求を実装する
アドバイス
このコンテンツは、.NET Docs で入手できる、またはオフラインで読み取ることができる無料のダウンロード可能な PDF として入手できる、コンテナー化された .NET アプリケーションの電子ブックである .NET マイクロサービス アーキテクチャからの抜粋です。
IHttpClientFactory は、DefaultHttpClientFactory
という信念に基づいたファクトリによって実装されるコントラクトで、.NET Core 2.1 以降、アプリケーションで使用するための HttpClient インスタンスを作成することができます。
.NET で使用できる元の HttpClient クラスに関する問題
元の既知の HttpClient クラスは簡単に使用できますが、多くの開発者が適切に使用していない場合があります。
このクラスは IDisposable
を実装しますが、using
ステートメント内で宣言およびインスタンス化することは推奨されません。これは、HttpClient
オブジェクトが破棄されると、基になるソケットがすぐに解放されず、ソケットの枯渇 問題につながる可能性があるためです。 この問題の詳細については、HttpClient を間違って使用していて、ソフトウェアのを不安定にしているブログ記事を参照してください。
そのため、HttpClient
は 1 回インスタンス化され、アプリケーションの有効期間を通じて再利用されることを意図しています。 すべての要求に対して HttpClient
クラスをインスタンス化すると、大量の負荷で使用可能なソケットの数が使い果たされます。 この問題により、SocketException
エラーが発生します。 この問題を解決するための考えられるアプローチは、HttpClient の使用方法に関するこの Microsoft の記事で説明されているように、シングルトンまたは静的として HttpClient
オブジェクトを作成に基づいています。 これは、1 日に数回実行される有効期間の短いコンソール アプリなどに適したソリューションです。
開発者が直面する別の問題は、長時間実行されるプロセスで HttpClient
の共有インスタンスを使用する場合です。 HttpClient がシングルトンまたは静的オブジェクトとしてインスタンス化される状況では、dotnet/runtime GitHub リポジトリの に関するこの の問題で説明されているように、DNS の変更を処理できません。
ただし、この問題は実際には HttpClient
にあるのではなく、HttpClient の既定のコンストラクターにあります。理由として、それによって、前述の "ソケット枯渇" および DNS 変更の問題を抱える、HttpMessageHandler の新しい具象インスタンスが作成されるということが挙げられます。
上記の問題に対処し、HttpClient
インスタンスを管理できるようにするために、.NET Core 2.1 では 2 つのアプローチが導入されました。そのうちの 1 つは IHttpClientFactory。 これは、依存関係の挿入 (DI) を使用してアプリで HttpClient
インスタンスを構成および作成するために使用されるインターフェイスです。 また、Polly ベースのミドルウェアが HttpClient でハンドラーの委任を利用するための拡張機能も提供します。
別の方法は、構成済みの PooledConnectionLifetime
と共に SocketsHttpHandler
を使うことです。 このアプローチは、有効期間の長いインスタンス、static
インスタンス、またはシングルトン HttpClient
インスタンスに適用されます。 さまざまな戦略の詳細については、.NET の HttpClient ガイドライン参照してください。
Polly は、開発者が事前に定義されたポリシーを流暢かつスレッド セーフな方法で使用することで、アプリケーションに回復性を追加するのに役立つ一時的な障害処理ライブラリです。
IHttpClientFactory を使用する利点
IHttpMessageHandlerFactoryも実装する IHttpClientFactoryの現在の実装には、次の利点があります。
- 論理
HttpClient
オブジェクトの名前付けと構成の中心となる場所を提供します。 たとえば、特定のマイクロサービスにアクセスするように事前に構成されたクライアント (サービス エージェント) を構成できます。 HttpClient
でハンドラーを委任し、Polly ベースのミドルウェアを実装して Polly のポリシーを利用して回復性を実現することで、発信ミドルウェアの概念を体系化します。HttpClient
には、送信 HTTP 要求用にリンクできるハンドラーを委任するという概念が既に含まれています。 HTTP クライアントをファクトリに登録できます。また、Polly ハンドラーを使用して、Retry や CircuitBreakers などの Polly ポリシーを使用できます。- HttpMessageHandler の有効期間を管理して、
HttpClient
の有効期間を自分で管理するときに発生する可能性がある問題を回避します。
ヒント
DI によって挿入された HttpClient
インスタンスは、関連付けられている HttpMessageHandler
がファクトリによって管理されるため、安全に破棄できます。 挿入された HttpClient
インスタンスは DI の観点から 一時的な ですが、HttpMessageHandler
インスタンスは スコープ付きと見なすことができます。 HttpMessageHandler
インスタンスには、アプリケーション スコープ (たとえば、ASP.NET 受信要求スコープ) とは別の、独自の DI スコープがあります。 詳細については、「.NET での HttpClientFactory の使用の」を参照してください。
手記
IHttpClientFactory
(DefaultHttpClientFactory
) の実装は、Microsoft.Extensions.DependencyInjection
NuGet パッケージの DI 実装に密接に関連付けられています。 DIなしまたは他のDI実装でHttpClient
を使用する必要がある場合は、static
を使用するか、PooledConnectionLifetime
を設定したシングルトンHttpClient
を使用することを検討してください。 詳細については、.NET の HttpClient ガイドライン参照してください。
IHttpClientFactory を使用する複数の方法
アプリケーションで IHttpClientFactory
を使用するには、いくつかの方法があります。
- 基本的な使用方法
- 名前付きクライアントを使用する
- 型指定されたクライアントを使用する
- 生成されたクライアントの使用
簡潔にするために、このガイダンスでは、型指定されたクライアント (サービス エージェント パターン) を使用する IHttpClientFactory
を使用する最も構造化された方法を示します。 しかし、オプションはすべてドキュメント化されており、現在、IHttpClientFactory
の使用方法が記載されたこちらの記事で一覧表示されています。
手記
アプリで Cookie が必要な場合は、アプリで IHttpClientFactory を使用しないようにすることをお勧めします。 クライアントを管理する別の方法については、HTTP クライアント を使用するためのガイドラインを参照してください。
IHttpClientFactory で型指定されたクライアントを使用する方法
では、"型指定されたクライアント" とは何でしょうか。 これは、特定の用途のために事前に構成された HttpClient
にすぎません。 この構成には、ベース サーバー、HTTP ヘッダー、タイムアウトなどの特定の値を含めることができます。
次の図は、IHttpClientFactory
で型指定されたクライアントがどのように使用されるかを示しています。
図 8-4. 型指定されたクライアント クラスで IHttpClientFactory
を使用する。
上の図では、(コントローラーまたはクライアント コードで使用される) ClientService
は、登録済みの IHttpClientFactory
によって作成された HttpClient
を使用します。 このファクトリでは、プールからの HttpClient
が HttpMessageHandler
に割り当てられます。 HttpClient
は、DI コンテナー内の IHttpClientFactory
を拡張メソッド AddHttpClientに登録するときに、Polly のポリシーを使用して構成できます。
上記の構造を構成するには、IServiceCollectionの AddHttpClient 拡張メソッドを含む Microsoft.Extensions.Http
NuGet パッケージをインストールして、アプリケーションに IHttpClientFactory を追加します。 この拡張メソッドは、インターフェイス IHttpClientFactory
のシングルトンとして使用する内部 DefaultHttpClientFactory
クラスを登録します。 HttpMessageHandlerBuilderの一時的な構成を定義します。 プールから取得されたこのメッセージ ハンドラー (HttpMessageHandler オブジェクト) は、ファクトリから返された HttpClient
によって使用されます。
次のスニペットでは、AddHttpClient()
を使用して、HttpClient
を使用する必要がある型指定されたクライアント (サービス エージェント) を登録する方法を確認できます。
// Program.cs
//Add http client services at ConfigureServices(IServiceCollection services)
builder.Services.AddHttpClient<ICatalogService, CatalogService>();
builder.Services.AddHttpClient<IBasketService, BasketService>();
builder.Services.AddHttpClient<IOrderingService, OrderingService>();
前のスニペットに示すようにクライアント サービスを登録すると、DefaultClientFactory
はサービスごとに標準の HttpClient
を作成します。 型指定されたクライアントは、DI コンテナーに一時的なものとして登録されます。 前のコードでは、AddHttpClient()
は CatalogService 、BasketService、OrderingService を一時的なサービスとして登録し、追加の登録を必要とせずに直接挿入して使用できるようにします。
登録のインスタンス固有の構成を追加することもできます。たとえば、次に示すように、ベース アドレスを構成し、いくつかの回復性ポリシーを追加します。
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
この次の例では、上記のいずれかのポリシーの構成を確認できます。
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
Polly の使用の詳細については、次の記事を参照してください。
HttpClient の有効期間
IHttpClientFactory
から HttpClient
オブジェクトを取得するたびに、新しいインスタンスが返されます。 各 HttpClient
は、HttpMessageHandler
の期限が切れていない限り、リソースの消費量を削減するために IHttpClientFactory
によってプールされ再利用される HttpMessageHandler
を使用します。
ハンドラーのプールは、各ハンドラーが通常、独自の基になる HTTP 接続を管理する場合に望ましいものです。必要以上に多くのハンドラーを作成すると、接続の遅延が発生する可能性があります。 また、一部のハンドラーは接続を無期限に開いたままにします。これにより、ハンドラーが DNS の変更に反応できなくなる可能性があります。
プール内の HttpMessageHandler
オブジェクトの有効期間は、プール内の HttpMessageHandler
インスタンスを再利用できる時間の長さです。 既定値は 2 分ですが、型指定されたクライアントごとにオーバーライドできます。 これをオーバーライドするには、次のコードに示すように、クライアントの作成時に返される IHttpClientBuilder で SetHandlerLifetime()
を呼び出します。
//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client
builder.Services.AddHttpClient<ICatalogService, CatalogService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
型指定された各クライアントは、独自に構成されたハンドラーの有効期間値を持つことができます。 ハンドラーの有効期限を無効にするには、有効期間を InfiniteTimeSpan
に設定します。
挿入された構成済みの HttpClient を使用する、型指定されたクライアント クラスを実装する
前の手順として、"BasketService"、"CatalogService"、"OrderingService" などのサンプル コードのクラスなど、型指定されたクライアント クラスを定義する必要があります。型指定されたクライアントは、HttpClient
オブジェクト (コンストラクターを介して挿入) を受け入れ、それを使用してリモート HTTP サービスを呼び出すクラスです。 例えば:
public class CatalogService : ICatalogService
{
private readonly HttpClient _httpClient;
private readonly string _remoteServiceBaseUrl;
public CatalogService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Catalog> GetCatalogItems(int page, int take,
int? brand, int? type)
{
var uri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl,
page, take, brand, type);
var responseString = await _httpClient.GetStringAsync(uri);
var catalog = JsonConvert.DeserializeObject<Catalog>(responseString);
return catalog;
}
}
型指定されたクライアント (この例ではCatalogService
) は DI (依存関係の挿入) によってアクティブ化されます。つまり、HttpClient
に加えて、コンストラクターで登録されたサービスを受け入れることもできます。
型指定されたクライアントは実質的に一時的なオブジェクトです。つまり、必要なたびに新しいインスタンスが作成されます。 構築されるたびに、新しい HttpClient
インスタンスを受け取ります。 ただし、プール内の HttpMessageHandler
オブジェクトは、複数の HttpClient
インスタンスによって再利用されるオブジェクトです。
型指定されたクライアント クラスを使用する
最後に、型指定されたクラスを実装したら、それらを登録し、AddHttpClient()
で構成することができます。 その後、次に示す eShopOnContainers のコードに示すように、Razor ページ コードや MVC Web アプリ コントローラーなど、DI によってサービスが挿入される場所でそれらを使用できます。
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
public class CatalogController : Controller
{
private ICatalogService _catalogSvc;
public CatalogController(ICatalogService catalogSvc) =>
_catalogSvc = catalogSvc;
public async Task<IActionResult> Index(int? BrandFilterApplied,
int? TypesFilterApplied,
int? page,
[FromQuery]string errorMsg)
{
var itemsPage = 10;
var catalog = await _catalogSvc.GetCatalogItems(page ?? 0,
itemsPage,
BrandFilterApplied,
TypesFilterApplied);
//… Additional code
}
}
}
ここまでのコード スニペットは、通常の HTTP 要求を実行する例のみを示しています。 ただし、次のセクションでは、指数バックオフによる再試行、サーキット ブレーカー、認証トークンを使用したセキュリティ機能、その他のカスタム機能など、HttpClient
によって行われたすべての HTTP 要求に回復性のあるポリシーを設定する方法を示します。 これらのすべてを行うには、ポリシーを追加し、登録済みの型指定されたクライアントにハンドラーを委任するだけです。
その他のリソース
.NET 用 HttpClient ガイドラインhttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
.NETにおけるHttpClientFactoryの使用方法 https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
ASP.NET Core での HttpClientFactory の使用 https://learn.microsoft.com/aspnet/core/fundamentals/http-requests
HttpClientFactory ソース コード
dotnet/runtime
GitHub リポジトリhttps://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (.NET の回復性と一時的な障害処理ライブラリ)https://thepollyproject.azurewebsites.net/
.NET