アプリ開発ときどきアウトドア

主にJavaを使ったアプリ開発やトラブルシューティング等のノウハウ、キャンプや登山の紹介や体験談など。

1. システムエンジニアリング ASP.NET Core 実装技術

ASP.NET Core: IHttpClientFactoryの使用方法

投稿日:2020年4月5日 更新日:


とりあえず、どんなサンプルになるか知りたい人は下記のサンプルをご覧ください。

ASP.NET Coreや.NET CoreでWebAPIの実行等のHTTP通信を行う場合はHttpClientを使用できますが、HtppClientが想定する利用シナリオを理解して使用しないと、大きな問題が発生するため、IHttpClientFactoryからHttpClientを生成する方法が推奨されます。
ここでは、IHttpClientFactoryを使ったHttpClientの使用方法について説明します。記載のベースになっているのは、マイクロソフトのこちらの記事です。
なお、想定している.NET Coreのバージョンは3.1です。

HttpClientの単独使用の問題点

HttpClientを使うことで容易にHTTP通信の処理を実装できますが、前提となる利用方法を理解せずに使用すると利用シナリオによっては次の問題が発生します。

IHttpClientFactoryの利点

上記の問題に対処するために、.NET Core 2.1からIHttpClientFactoryが導入されており、このインターフェイスを介して作成したHttpClientの使用が推奨されます。
IHttpClientFactoryの利点は次の通りです。

  1. HttpClientの名前付けと構成を一元管理できる。
    HttpClientの種類を識別するための名前を付けて、HttpClientを取得できます。HttpClientを使用する前に、名前毎に異なる既定値を設定することもできます。例えばWebAPのAとBに接続するためのHttpClientをそれぞれ”weapi-a”, “webapi-b”という名前でIHttpClientFactoryに登録し、”webapi-a”, “webapi-b”に対してURLや認証情報を事前に設定しておくことができます。
  2. デリゲートハンドラによる通信処理の拡張
    HTTP通信時の入出力をカスタマイズするためのデリゲートハンドラ(DelegatingHandler)を、パイプラインとして登録できます。デリゲートハンドラを使用することで、HTTP送信前後で独自にHTTPヘッダやボディの編集・検証等を実装できます。
  3. 高度なエラー処理用のPollyライブラリの統合
    HTTPをベースとしたクラウドベースのサービス利用では、アプリケーションの信頼性・安定性を向上するために、HTTP通信エラーに対する高度なハンドリングが必要となります。IHttpClientFactoryでは、このようなハンドリングが行えるオープンソースライブラリであるPollyを使用できるようになっています。
  4. HttpMessageHandlerの管理が可能
    HttpClientは内部でHttpMessageHandlerを使用してHTTP通信を行っています。
    前述のDNS変更の問題は、HttpMessageHandlerインスタンスの保持期間が無制限になっていることが問題だったのですが、このインスタンスのプーリングやライフサイクルの管理が可能になっています。

IHttpClientFactoryの使用方法

IHttpClientFactoryを介したHttpClientの使用方法は次の4種類がある。
ちょっとした検証で使うなら「基本的な方法」で十分かと思いますが、実業務で使うならHTTP通信+接続先固有の処理をカプセル化できDI可能な「型付きクライアント」がお薦めです。

使用方法 説明
1. 基本的な方法
(検証等で単純に使用する場合)
HttpClientをシンプルに使用する方法。

Startup.csで下記の宣言を行い、コントローラ等で使用する場合はコンストラクタでIHttpClientFactoryをDIしてからHttpClientを生成する。

(宣言時): services.AddHttpClient();
(使用時): HttpClient client = _clientFactory.CreateClient();
2. 名前付きクライアント
(接続先や構成パターンが多数ある場合や接続先が動的に変更される場合等)
クライアントの種類毎に名前(文字列)を付けて使用する方法。

Startup.csで名前を指定してクライアントを登録・設定を宣言し、コントローラ等で使用する場合はコンストラクタでIHttpClientFactoryをDIしてから名前を指定してHttpClientを生成する。
(宣言時):
services.AddHttpClient("name", client => { ... });
(使用時):
HttpClient client = _clientFactory.CreateClient("name");
3. 型付きクライアント
(接続先や構成パターンが限定な場合、送受信時の業務ロジックを実装してその内容を隠蔽したい場合等)
独自のクライアントクラスを使用する方法。
Startup.csでクラス名を指定してクライアントを登録・設定を宣言し、コントローラ等で使用する場合はコンストラクタでクライアントをDIして使用する。(IHttpClientFactoryのDIは不要)

(宣言時):
services.AddHttpClient<GitHubService>();
(使用時):
public SomeConstructor(GitHubService client){ ... };
4. 生成されたクライアント
(要件がライブラリで満たされる場合、実装量を削減したい場合等)
サードパーティーライブラリのクライアントを使用する方法。
IHttpClientFactoryにサードパーティーライブラリを登録することで実現する。クライアントの使用方法はライブラリに依存するが、基本的にはDIで使用可能となる。下記はRefitを使用する場合の例です。

(宣言時):
services.AddHttpClient("hello", client => { .. })
.AddTypedClient(client => Refit.RestService.For<IHelloClient>(client) );
(使用時):
public SomeConstructor(IHelloClient client){ ... };

参考として、MSが提供する型付きクライアントの構成イメージを説明します。
Microsoftより
IHttpClientFactoryにClientServiceを登録することで、コントローラやクライアントにClientServiceをDIできます。(コントローラやクライアントはClientServiceを直接DIできるので、IHttpClientFactoryをDIする必要はありません。)
ClientService内ではHttpClientインスタンスを使用して通信を行いますが、実際の通信処理はプールから取得したHttpMessageHandlerインスタンスを使って行われます。送信の前後で、クライアントに登録されているデリゲートハンドラが要求や応答を編集・検証します。エラーハンドリングに関しては、ポリシーとして登録されているPollyのエラーハンドリングが行われます。

カスタマイズ方法

ここでは前述のようにHttpClientをどう使うか?ではなく、HttpClientそのものの動作のカスタマイズについて説明します。カスタマイズ可能なポイントは次の通りです。

  • HttpClientのデフォルトプロパティ
  • Pollyによるエラーハンドリング
  • デリゲートハンドラの登録
  • HttpMessageHandlerライフサイクルの管理
  • HttpMessageHandlerのプロパティ

HttpClientのデフォルトプロパティ

クライアント登録時に、HttpClientの既定のプロパティを設定できます。
設定可能なHTTPヘッダはMSのリファレンスを参照のこと。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("github", client =>
    {
        client.BaseAddress = new Uri("https://example.com/");
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.CacheControl =
            new CacheControlHeaderValue(){
                NoCache = true,
                NoStore = true,
                MaxAge = new TimeSpan(0)
        };
        client.DefaultRequestHeaders.Add("Authorization", "Bearer xxx");
        client.Timeout = TimeSpan.FromSeconds(3 * 60);
    });
}

Pollyによるエラーハンドリング

応答コードやエラーに対するエラーハンドリング方法をポリシーとして指定します。
応答コード5xxや408、例外(HttpRequestException)を纏めてエラーハンドリングする場合はAddTransientHttpErrorPolicyを使用します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

応答コードや例外のそれぞれに異なるエラーハンドリングを行いたい場合はAddPolicyHandlerを使用します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<ICatalogService, CatalogService>()
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(...);
}

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

デリゲートハンドラの登録

デリゲートハンドラクラスを作成し、Startupで登録します。
デリゲートハンドラクラスを作成するためにはDelegatingHandlerを継承したクラスを作成し、SendAsyncメソッドを実装します。

public class SecureRequestHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (request.RequestUri.Scheme == Uri.UriSchemeHttp)
        {
            var builder = new UriBuilder(request.RequestUri) { Scheme = Uri.UriSchemeHttps };
            request.RequestUri = builder.Uri;
        }

        return base.SendAsync(request, cancellationToken);
    }
}

Startup.csにて、定義したデリゲートハンドラクラスをシングルトンとして登録し、特定のクライアントに対してAddHttpMessageHandlerメソッドでデリゲートハンドラクラスを登録します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<SecureRequestHandler>();
    services.AddTransient<RequestDataHandler>();

    services.AddHttpClient("clientwithhandlers")
        .AddHttpMessageHandler<SecureRequestHandler>()
        .AddHttpMessageHandler<RequestDataHandler>();

デリゲートハンドラの完全なコードはMSのサンプルを参照のこと。

HttpMessageHandlerライフサイクルの管理

HttpMessageHandlerのプール上での生存時間を指定します。(既定は2分です。)

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

HttpMessageHandlerのプロパティ

HttpClientの詳細のカスタマイズを行いたい場合、内部で使用されるHttpMessageHandlerをカスタマイズします。Cookieの使用についてもカスタマイズできます。
使用できるプロパティはMSのリファレンスを参照のこと。(以前はHttpClientHandlerで構成しましたが、.NET Core 2.1以降ではSocketsHttpHandlerを使用するそうです。)

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("configured-inner-handler")
        .ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler()
            {
                AllowAutoRedirect = false,
                UseDefaultCredentials = true
            };
        }
    );

その他の参考情報

サンプル

私が作成したサンプルの説明記事です。

Pollyについて

Polly関連の補足説明です。

  • Pollyで実装されているエラーハンドリングのパターンはAzureのクラウド設計パターンが参考になると思います。
    • 再試行: 一時的な障害をアプリケーションが処理できるようにする。Azureサービスを使用する際の再試行ポリシーはこちらを参考のこと。
    • サーキットブレーカー: 直ぐに回復しない障害に対して再試行すると状況を悪化させる場合があります。このような状況にならないよう、失敗する可能性がある操作をアプリケーションが実行しないようにする。
    • バルクヘッド: 接続プールを分離し、特定サービスへの接続で障害が起きても、別のサービスへの接続に影響ないようにする。
      接続プールを共有する場合、例えばあるサービスに対して大量の接続が行われると、接続プール上の接続が空になってしまい、他のサービスへの接続ができなくなってしまう。接続プールを分離することで、あるサービスの影響が他サービスの利用に影響しないようにできる。
      バルクヘッドとは船の区画(隔壁)の意で、船体の特定区画が破損して浸水しても、他の区画には浸水させず船が沈むのを防ぐ。
  • IHttpClientFactory ポリシーと Polly ポリシーで指数バックオフを含む HTTP 呼び出しの再試行を実装する
  • サーキット ブレーカー パターンを実装する


(adsbygoogle = window.adsbygoogle || []).push({});


(adsbygoogle = window.adsbygoogle || []).push({});

-1. システムエンジニアリング, ASP.NET Core, 実装技術

執筆者:

関連記事

Java/JavaEE開発キットの作成

Javaアプリ開発者のローカルPCに、ファイル展開するだけで開発環境をできるようにするための開発キットの準備について記載します。 ★随時更新予定★ 背景 Java/JavaEE等を使ったアプリ開発では …

vbaでのエンコード/デコードのサンプル

Excel(vba)で、MD5/SHA-1/SHA-2(SHA-256)の出力、Hex/Base64エンコード/デコードを調べたので備忘録として残します。 動作検証した環境は、Windows10+Of …

Webアプリテスト用のHTTPヘッダの追加

フロントに配置されたリバースプロキシサーバやロードバランサで設定されたHTTPヘッダを使用するWebアプリを開発することが多々あります。 このようなシナリオでは、設計に基づいて実装することはできますが …

wildflyアップロードサイズ上限

多くのWebサーバやAPサーバでは、サーバリソースの過剰な消費やDoS攻撃に対する対処として、アップロードの最大サイズが決められている。(厳密には、ブラウザから入力値やファイル等のデータを送信するため …

Apache FTPClientでのコマンドのトレース

Apache FTPClientを使って、JavaアプリケーションからFTP操作が可能となります。 この際、FTPクライアントとFTPサーバ間との通信内容をログに出力する方法を説明します。 概要 Ap …