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

HttpClientFactory は、自己主張性の強いファクトリで、アプリケーションに使用する HttpClient インスタンスを作成するため、.NET Core 2.1 以降で使用できます。HttpClientFactory is 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.

1 つ目の問題は、このクラスは破棄可能ですが、HttpClient オブジェクトを破棄しても、基になるソケットがすぐに解放されず、'ソケットの枯渇' という重大な問題が発生する場合があるため、using ステートメントで使用するのは最適な選択ではないということです。As a first issue, while this class is disposable, using it with the using statement is not the best choice because even when you dispose HttpClient object, the underlying socket is not immediately released and can cause a serious issue named ‘sockets exhaustion’. この問題の詳細については、ブログ記事「You're using HttpClient wrong and it is destabilizing your software (HttpClient の誤った使い方がソフトウェアを不安定にする)」を参照してください。For more information about this issue, see You're using HttpClient wrong and it's destabilizing your software blog post.

そのため、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.

しかし、HttpClient には、シングルトンまたは静的オブジェクトとして使用した場合に発生する可能性がある 2 つ目の問題があります。But there’s a second issue with HttpClient that you can have when you use it as singleton or static object. この場合、dotnet/corefx GitHub リポジトリでこの問題について説明されているように、シングルトンまたは静的 HttpClient では、DNS の変更が尊重されません。In this case, a singleton or static HttpClient doesn't respect DNS changes, as explained in this issue at the dotnet/corefx GitHub repository.

これらの問題に対処し、HttpClient インスタンスの管理を容易にするため、.NET Core 2.1 では、新しい HttpClientFactory が導入されています。これは、Polly を統合することで、回復力のある HTTP 呼び出しを実装するためにも使用できます。To address those mentioned issues and make the management of HttpClient instances easier, .NET Core 2.1 introduced a new HttpClientFactory that can also be used to implement resilient HTTP calls by integrating Polly with it.

Polly は、事前に定義されたポリシーを緩やかでスレッドセーフな方法で使用することにより、開発者がアプリケーションに回復性を追加できるようにするための一時的な障害処理ライブラリです。Polly is 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.

HttpClientFactory とはWhat is HttpClientFactory

HttpClientFactory は次の目的のために設計されています。HttpClientFactory is designed to:

  • 論理 HttpClient オブジェクトの名前付けと構成を行うために、中央の場所を提供します。Provide 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 register HTTP clients into the factory and you can use a Polly handler to use Polly policies for Retry, CircuitBreakers, and so on.
  • HttpClientMessageHandlers の有効期間を管理して、HttpClient の有効期間を自分で管理する際に発生する可能性がある上記の問題を回避します。Manage the lifetime of HttpClientMessageHandlers to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

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

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

  • HttpClientFactory を直接使用するUse HttpClientFactory Directly
  • 名前付きクライアントを使用するUse Named Clients
  • 型指定されたクライアントを使用するUse Typed Clients
  • 生成されたクライアントを使用するUse Generated Clients

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

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

それでは、"型指定されたクライアント" とは何でしょうか。So, what's a "Typed Client"? これは、挿入時に DefaultHttpClientFactory によって構成される単なる HttpClient です。It's just an HttpClient that's configured upon injection by the DefaultHttpClientFactory.

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

(コントローラーまたはクライアント コードによって使用される) ClientService には、登録済みの IHttpClientFactory によって作成された HttpClient が使用されます。

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

まず、IServiceCollection に対する AddHttpClient() 拡張メソッドを含む Microsoft.Extensions.Http NuGet パッケージをインストールすることで、アプリケーションに HttpClientFactory を設定します。First, setup HttpClientFactory 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 DefaultHttpClientFactory 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), meaning that it can accept any registered service in its constructor, in addition to HttpClient.

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

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

最後に、型指定されたクラスを実装し、AddHttpClient() に登録したら、DI によってサービスを挿入できる場所であればどこでもそれらを使用できます。Finally, once you have your typed classes implemented and have them registered with AddHttpClient(), you can use them wherever you can 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 要求を実行するだけですが、次のセクションでは、不思議なことが起こります。ポリシーを追加して、ハンドラーを登録済みの型指定されているクライアントにデリゲートするだけで、HttpClient によって実行されるすべての HTTP 要求が、指数バックオフを含む再試行、サーキット ブレーカー、またはその他の任意のカスタム デリゲート ハンドラーなど、回復力のあるポリシーを考慮して、追加のセキュリティ機能 (認証トークンの使用など) やその他のカスタム機能を実装するようになります。Up to this point, the code shown is just performing regular Http requests, but the ‘magic’ comes in the following sections where, just by adding policies and delegating handlers to your registered Typed Clients, all the HTTP requests to be done by HttpClient will behave taking into account resilient policies such as retries with exponential backoff, circuit breakers, or any other custom delegating handler to implement additional security features, like using auth tokens, or any other custom feature.

その他の技術情報Additional resources