C Sharpとネットワーク - HttpClient

提供:MochiuWiki : SUSE, EC, PCB
2024年1月23日 (火) 09:22時点におけるWiki (トーク | 投稿記録)による版 (→‎サンプルコード)
ナビゲーションに移動 検索に移動

概要

HttpClientクラスは、HTTPリクエストを投げる場合に使用するクラスである。

.NET Framework 4.0以前では、それまではHttpWebRequestクラス、WebClientが使用されていた。
HttpClientクラスは.NET Framework 4.5以降から提供された機能であり、簡単にHTTPリクエストを投げることができるクラスとして追加された。


HttpClientクラスの仕様

HttpClientクラスのインスタンスを生成する時、内部では新しいソケットを開く。
したがって、メソッド内でHttpClientクラスのインスタンスを生成する場合、常に新しいソケットを開くため、リソースを消費することになる。

HttpClientクラスのインスタンスを破棄した場合、ソケットが閉じるタイミングは、状態がTIME_WAITに遷移して、暫く時間が経つと自動的に解放される。

これは、リクエストする頻度が少ない場合は問題無いが、大量にリクエストを行う場合は大きなボトルネックとなる。


アンチパターン

HttpClientクラス

HttpClientクラスのインスタンスの生成において、IDisposableインターフェースを実装しているのでusingブロックで囲うものがある。

しかし、これは通信を実行するごとにソケットを開くことにより、大量のリソースを消費してリソースが枯渇する場合がある。

 using (var client = new HttpClient())
 {
    await client.PostAsync("http://iketeru-service.com/");
 }


HttpRequestMessageクラス

固定のリクエストヘッダや認証情報を付加したHttpRequestMessageクラスを使用する場合、共通の内部メソッドであるCreateRequest()を使用する。
これは、HttpRequestMessageクラスのインスタンスを生成した後、SendAsync()メソッドを使用してメッセージを送信する。

 var getReult = await client.GetAsync("http://kirakira-service.com/");
 var postRsult = await client.PostAsync("http://sugoi-service.com/");



解決策

HttpClientクラスは、privateキーワードおよびstaticキーワードを指定したプロパティとして持つ必要がある。

Microsoftの公式ドキュメント不適切なインスタンス化のアンチパターンの中でこの問題について取り上げており、
HttpClientを使用した実装をする時は、インスタンスを静的変数(static)にして使用するとの記載がある。


サンプルコード

まず、HttpClientクラスのオブジェクトを生成する。
この時、タイムアウトの設定等はコンストラクタで行う必要がある。

複数のHttoClientクラスを使用して同時に実行する場合も、HttpClientはそのような使用を想定した設計となっている。

ただし、staticキーワードを付加する場合、DNSの変更が反映されず、HttpClientクラスは(HttpClientHandlerクラスを通じて)、ソケットが閉じるまでコネクションを無制限に使用し続ける。
HttpClientクラスは、DNS TTLを尊重しており、デフォルトではこの値は1時間である。
1時間過ぎれば、HttpClientクラスはDNSのエントリが有効であることを検証して、必要に応じて更新されたIPアドレスに対して新しいコネクションを作成する。

そのため、HttpClientクラスのオブジェクトに、コネクションを自動的にリサイクルするように指定する。
これは、アプリケーションの起動時において、アプリケーションで接続する全てのエンドポイント向けに1度だけ行う。 (エンドポイントが実行時に決まる場合は、決定する時に行う必要がある)
時間は、1分〜5分程度に設定する方がよい。 (ホスト、ポート、スキーマが重要である)

 class SampleClass
 {
    private static readonly HttpClient httpclient = null;
    
    static SampleClass()
    {
        httpclient = new HttpClient();
    }

    public async Task<SomeResponse> CallAPIAsync()
    {
       var sp = ServicePointManager.FindServicePoint(new Uri("{URL}"));
       sp.ConnectionLeaseTimeout = 60 * 1000;  // コネクションのリサイクル時間 : 1分
 
       await httpclient.PostAsync("{URL}");
 
       // ...略
    }
 }


また、1つのHttpClientクラスは1つのソケット(1つのホスト)として使用した方がよいため、
異なるホストにもリクエストを投げる場合は、別のHttpClientクラスのオブジェクトを生成する方がよい。