IHttpClientFactory を使用して回復力の高い HTTP 要求を実装するUse IHttpClientFactory to implement resilient HTTP requests

IHttpClientFactory はコントラクトであり、ご利用のアプリケーションで使用する HttpClient インスタンスを作成するために .NET Core 2.1 以降で使用できる自己主張の強いファクトリ DefaultHttpClientFactory によって実装されます。IHttpClientFactory is a contract implemented by DefaultHttpClientFactory, an opinionated factory, available since .NET Core 2.1, for creating HttpClient instances to be used in your applications.

.NET Core で使用可能な元の HttpClient クラスに関する問題Issues with the original HttpClient class available in .NET Core

よく知られている元の HttpClient クラスは、簡単に使用できますが、場合によっては、多くの開発者が適切に使用していません。The original and well-known HttpClient class can be easily used, but in some cases, it isn't being properly used by many developers.

このクラスでは IDisposable が実装されますが、これを using ステートメント内で宣言およびインスタンス化することはお勧めできません。その理由は、HttpClient オブジェクトが破棄されても、基になるソケットがすぐに解放されず、"ソケットの枯渇" の問題が発生する可能性があるということにあります。Though this class implements IDisposable, declaring and instantiating it within a using statement is not preferred because when the HttpClient object gets disposed of, the underlying socket is not immediately released, which can lead to a socket exhaustion problem. この問題の詳細については、ブログ記事「HttpClient の誤った使い方がソフトウェアを不安定にする」を参照してください。For more information about this issue, see the blog post You're using HttpClient wrong and it's destabilizing your software.

そのため、HttpClient は一度インスタンス化されたら、アプリケーションの有効期間にわたって再利用されることを目的としています。Therefore, HttpClient is intended to be instantiated once and reused throughout the life of an application. すべての要求に対して HttpClient クラスをインスタンス化すると、高負荷の下で使用可能なソケットの数が枯渇してしまいます。Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. この問題により、SocketException エラーが発生します。That issue will result in SocketException errors. この問題を解決するために可能なアプローチは、HttpClient クライアントの使用に関するこの Microsoft の記事で説明されているように、HttpClient オブジェクトをシングルトンまたは静的として作成することに基づいています。Possible approaches to solve that problem are based on the creation of the HttpClient object as singleton or static, as explained in this Microsoft article on HttpClient usage. これは、1 日に数回実行される有効期間の短いコンソールアプリまたは類似のものに適したソリューションとなります。This can be a good solution for short-lived console apps or similar, that run a few times a day.

開発者が遭遇するもう 1 つの問題は、長時間実行されるプロセスで HttpClient の共有インスタンスを使用するタイミングです。Another issue that developers run into is when using a shared instance of HttpClient in long-running processes. HttpClient がシングルトンまたは静的オブジェクトとしてインスタンス化される状況では、dotnet/runtime GitHub リポジトリのこちらの問題で説明されているように、DNS の変更を処理できません。In a situation where the HttpClient is instantiated as a singleton or a static object, it fails to handle the DNS changes as described in this issue of the dotnet/runtime GitHub repository.

ただし、この問題は実際には HttpClient にあるのではなく、HttpClient の既定のコンストラクターにあります。理由として、それによって、前述の "ソケット枯渇" および DNS 変更の問題を抱える、HttpMessageHandler の新しい具象インスタンスが作成されるということが挙げられます。However, the issue isn't really with HttpClient per se, but with the default constructor for HttpClient, because it creates a new concrete instance of HttpMessageHandler, which is the one that has sockets exhaustion and DNS changes issues mentioned above.

上記の問題に対処し、HttpClient インスタンスを管理しやすくするために、.NET Core 2.1 では、IHttpClientFactory インターフェイスが導入されました。これを使用すれば、依存関係の挿入 (DI) を介してアプリ内で HttpClient インスタンスを構成および作成することができます。To address the issues mentioned above and to make HttpClient instances manageable, .NET Core 2.1 introduced the IHttpClientFactory interface which can be used to configure and create HttpClient instances in an app through Dependency Injection (DI). HttpClient でのハンドラーのデリゲートを利用するために、Polly ベースのミドルウェアに対する拡張機能も提供されます。It also provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.

Polly は、事前に定義されたポリシーを緩やかでスレッドセーフな方法で使用することにより、開発者がアプリケーションに回復性を追加できるようにするための一時的な障害処理ライブラリです。Polly is a transient-fault-handling library that helps developers add resiliency to their applications, by using some pre-defined policies in a fluent and thread-safe manner.

IHttpClientFactory を使用する利点Benefits of using IHttpClientFactory

IHttpClientFactory の現在の実装 (これによって IHttpMessageHandlerFactory も実装される) には、次のような利点があります。The current implementation of IHttpClientFactory, that also implements IHttpMessageHandlerFactory, offers the following benefits:

  • 論理 HttpClient オブジェクトの名前付けと構成を行うために、中央の場所が提供されます。Provides a central location for naming and configuring logical HttpClient objects. たとえば、特定のマイクロサービスにアクセスするために事前に構成されているクライアント (サービス エージェント) を構成できます。For example, you may configure a client (Service Agent) that's pre-configured to access a specific microservice.
  • HttpClient でのハンドラーのデリゲートにより送信ミドルウェアの概念を体系化し、Polly ベースのミドルウェアを実装して、回復性のための Polly のポリシーを利用します。Codify the concept of outgoing middleware via delegating handlers in HttpClient and implementing Polly-based middleware to take advantage of Polly's policies for resiliency.
  • HttpClient は既に、送信 HTTP 要求用にリンクできるハンドラーのデリゲートの概念を備えています。HttpClient already has the concept of delegating handlers that could be linked together for outgoing HTTP requests. ファクトリに HTTP クライアントを登録できるほか、Polly ハンドラーを使用して、再試行、サーキット ブレーカーなどに Polly ポリシーを使用することができます。You can register HTTP clients into the factory and you can use a Polly handler to use Polly policies for Retry, CircuitBreakers, and so on.
  • HttpMessageHandler の有効期間を管理して、HttpClient の有効期間を自分で管理する際に発生する可能性がある上記の問題を回避します。Manage the lifetime of HttpMessageHandler to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

ヒント

DI によって挿入された HttpClient インスタンスは、関連付けられた HttpMessageHandler がファクトリによって管理されるため、安全に破棄できます。The HttpClient instances injected by DI, can be disposed of safely, because the associated HttpMessageHandler is managed by the factory. 実際、挿入された HttpClient インスタンスは、DI パースペクティブから "範囲指定" されます。As a matter of fact, injected HttpClient instances are Scoped from a DI perspective.

注意

IHttpClientFactory の実装 (DefaultHttpClientFactory) は、Microsoft.Extensions.DependencyInjection NuGet パッケージ内の DI の実装に緊密に関連付けられています。The implementation of IHttpClientFactory (DefaultHttpClientFactory) is tightly tied to the DI implementation in the Microsoft.Extensions.DependencyInjection NuGet package. その他の DI コンテナーの使用に関する詳細については、こちらの GitHub のディスカッションを参照してください。For more information about using other DI containers, see this GitHub discussion.

IHttpClientFactory を使用する複数の方法Multiple ways to use IHttpClientFactory

アプリケーションで IHttpClientFactory を使用するには、複数の方法があります。There are several ways that you can use IHttpClientFactory in your application:

  • 基本的な使用方法Basic usage
  • 名前付きクライアントを使用するUse Named Clients
  • 型指定されたクライアントを使用するUse Typed Clients
  • 生成されたクライアントを使用するUse Generated Clients

簡潔にするために、このガイダンスでは、型指定されたクライアント (サービス エージェント パターン) を使用する、IHttpClientFactory を使用するための最も構造化された方法を示します。For the sake of brevity, this guidance shows the most structured way to use IHttpClientFactory, which is to use Typed Clients (Service Agent pattern). しかし、オプションはすべてドキュメント化されており、現在、IHttpClientFactory の使用方法が記載されたこちらの記事で一覧表示されています。However, all options are documented and are currently listed in this article covering the IHttpClientFactory usage.

IHttpClientFactory で型指定されたクライアントを使用する方法How to use Typed Clients with IHttpClientFactory

それでは、"型指定されたクライアント" とは何でしょうか。So, what's a "Typed Client"? これは、特定の用途に合わせて事前に構成された HttpClient にすぎません。It's just an HttpClient that's pre-configured for some specific use. この構成には、ベース サーバー、HTTP ヘッダー、またはタイムアウトなどの特定の値を含めることができます。This configuration can include specific values such as the base server, HTTP headers or time outs.

次の図は、IHttpClientFactory で型指定されたクライアントを使用する方法を示しています。The following diagram shows how Typed Clients are used with IHttpClientFactory:

IHttpClientFactory で型指定されたクライアントを使用する方法を示している図。

図 8-4Figure 8-4. 型指定されたクライアント クラスで IHttpClientFactory を使用するUsing IHttpClientFactory with Typed Client classes.

上の図では、(コントローラーまたはクライアント コードによって使用される) ClientService によって、登録済みの IHttpClientFactory によって作成された HttpClient が使用されます。In the above image, a ClientService (used by a controller or client code) uses an HttpClient created by the registered IHttpClientFactory. このファクトリでは、プールからの HttpClientHttpMessageHandler に割り当てられます。This factory assigns an HttpMessageHandler from a pool to the HttpClient. 拡張メソッド AddHttpClient を使用して IHttpClientFactory を DI コンテナーに登録するときに、Polly のポリシーを使用して HttpClient を構成できます。The HttpClient can be configured with Polly's policies when registering the IHttpClientFactory in the DI container with the extension method AddHttpClient.

上の構造を構成するには、IServiceCollection に対する AddHttpClient 拡張メソッドを含む Microsoft.Extensions.Http NuGet パッケージをインストールすることで、アプリケーションに IHttpClientFactory を追加します。To configure the above structure, add IHttpClientFactory in your application by installing the Microsoft.Extensions.Http NuGet package that includes the AddHttpClient extension method for IServiceCollection. この拡張メソッドでは、インターフェイス IHttpClientFactory のシングルトンとして使用される内部 DefaultHttpClientFactory クラスが登録されます。This extension method registers the internal DefaultHttpClientFactory class to be used as a singleton for the interface IHttpClientFactory. これは HttpMessageHandlerBuilder の一時的な構成を定義します。It defines a transient configuration for the HttpMessageHandlerBuilder. プールから取得されたこのメッセージ ハンドラー (HttpMessageHandler オブジェクト) は、ファクトリから返された HttpClient によって使用されます。This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.

次のコードで、AddHttpClient() を使用して、HttpClient を使用する必要がある型指定されたクライアント (エージェント サービス) を登録する方法を確認できます。In the next code, you can see how AddHttpClient() can be used to register Typed Clients (Service Agents) that need to use HttpClient.

// Startup.cs
//Add http client services at ConfigureServices(IServiceCollection services)
services.AddHttpClient<ICatalogService, CatalogService>();
services.AddHttpClient<IBasketService, BasketService>();
services.AddHttpClient<IOrderingService, OrderingService>();

前のコードに示されているように、クライアント サービスを登録すると、DefaultClientFactory によって、サービスごとに標準の HttpClient が作成されるようになります。Registering the client services as shown in the previous code, makes the DefaultClientFactory create a standard HttpClient for each service.

また、次のコードに示すように、登録にインスタンス固有の構成を追加し、ベース アドレスの構成や回復性ポリシーの追加などを行うこともできます。You could also add instance-specific configuration in the registration to, for example, configure the base address, and add some resiliency policies, as shown in the following code:

services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
    client.BaseAddress = new Uri(Configuration["BaseUrl"]);
})
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

例として、次のコードで上記のポリシーの 1 つを確認できます。Just for the example sake, you can see one of the above policies in the next code:

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 の使用に関する詳細を確認できます。You can find more details about using Polly in the Next article.

HttpClient の有効期間HttpClient lifetimes

IHttpClientFactory から HttpClient オブジェクトを取得するたび、新しいインスタンスが返されます。Each time you get an HttpClient object from the IHttpClientFactory, a new instance is returned. ただし、各 HttpClient では、HttpMessageHandler の有効期間が切れていない限り、リソースの消費量を減らすために IHttpClientFactory によってプールおよび再利用されている HttpMessageHandler を使用します。But each HttpClient uses an HttpMessageHandler that's pooled and reused by the IHttpClientFactory to reduce resource consumption, as long as the HttpMessageHandler's lifetime hasn't expired.

通常各ハンドラーは基になる HTTP 接続を独自に管理しており、必要以上に多くのハンドラーを作成すると接続が遅延する可能性があるため、ハンドラーをプールするのは望ましい方法です。Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections; creating more handlers than necessary can result in connection delays. また、一部のハンドラーは接続を無期限に開いており、DNS の変更にハンドラーが対応できないことがあります。Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

プール内の HttpMessageHandler オブジェクトには、有効期間があります。この有効期間は、プール内の HttpMessageHandler インスタンスを再利用できる期間です。The HttpMessageHandler objects in the pool have a lifetime that's the length of time that an HttpMessageHandler instance in the pool can be reused. 既定値は 2 分ですが、型指定されたクライアントごとにオーバーライドできます。The default value is two minutes, but it can be overridden per Typed Client. オーバーライドするには、次のコードに示すように、クライアント作成時に返された IHttpClientBuilderSetHandlerLifetime() を呼び出します。To override it, call SetHandlerLifetime() on the IHttpClientBuilder that's returned when creating the client, as shown in the following code:

//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client
services.AddHttpClient<ICatalogService, CatalogService>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

型指定された各クライアントには、独自の構成済みハンドラーの有効期間の値を設定できます。Each Typed Client can have its own configured handler lifetime value. ハンドラーの有効期限を無効にするには、有効期間を InfiniteTimeSpan に設定します。Set the lifetime to InfiniteTimeSpan to disable handler expiry.

挿入された構成済みの HttpClient を使用する、型指定されたクライアント クラスを実装するImplement your Typed Client classes that use the injected and configured HttpClient

前の手順では、型指定されたクライアント クラス (サンプル コードにある、'BasketService'、'CatalogService'、'OrderingService' のようなクラスなど) が定義されている必要がありました。型指定されたクライアントは、(そのコンストラクターを通じて挿入された) HttpClient オブジェクトを受け取り、それを使用していくつかのリモート HTTP サービスを呼び出すクラスです。As a previous step, you need to have your Typed Client classes defined, such as the classes in the sample code, like 'BasketService', 'CatalogService', 'OrderingService', etc. – A Typed Client is a class that accepts an HttpClient object (injected through its constructor) and uses it to call some remote HTTP service. 次に例を示します。For example:

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 だけでなく、そのコンストラクター内に登録されている任意のサービスを受け取ることができます。The Typed Client (CatalogService in the example) is activated by DI (Dependency Injection), that means it can accept any registered service in its constructor, in addition to HttpClient.

型指定されたクライアントは実質的に一時的なオブジェクトであるため、新しいインスタンスは必要になるたびに作成されます。A Typed Client is effectively a transient object, that means a new instance is created each time one is needed. 作成されるたびに、新しい HttpClient インスタンスを受け取ります。It receives a new HttpClient instance each time it's constructed. ただし、プール内の HttpMessageHandler オブジェクトは、複数の HttpClient 要求で再利用されるオブジェクトです。However, the HttpMessageHandler objects in the pool are the objects that are reused by multiple HttpClient instances.

型指定されたクライアント クラスを使用するUse your Typed Client classes

最後に、型指定されたクラスを実装したら、AddHttpClient() に登録して構成できます。Finally, once you have your typed classes implemented, you can have them registered and configured with AddHttpClient(). その後、DI によってサービスが挿入される場所であればどこでもそれらを使用できます。After that you can use them wherever have services injected by DI. たとえば、次の eShopOnContainers のコードのように、Razor ページ コード、または MVC Web アプリのコントローラーなどがあります。For example, in a Razor page code or controller of an MVC web app, like in the following code from eShopOnContainers:

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 要求を実行する例のみが示されています。Up to this point, the above code snippet has only shown the example of performing regular HTTP requests. ただし、この "魔法" は、HttpClient によって行われるすべての HTTP 要求に、エクスポネンシャル バックオフによる再試行、回路遮断器、認証トークンを使用したセキュリティ機能、さらにはその他のあらゆるカスタム機能など、回復性があるポリシーが与えられるしくみについて説明する後続のセクションで登場します。But the 'magic' comes in the following sections where it shows how all the HTTP requests made by HttpClient, can have resilient policies such as retries with exponential backoff, circuit breakers, security features using auth tokens, or even any other custom feature. いずれも、ポリシーを追加し、登録済みの型指定されたクライアントにハンドラーを委任するだけで完了できます。And all of these can be done just by adding policies and delegating handlers to your registered Typed Clients.

その他の技術情報Additional resources