「C Sharpとネットワーク - HttpClient」の版間の差分

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動
18行目: 18行目:
==== HttpClientクラス ====
==== HttpClientクラス ====
<code>HttpClient</code>クラスのインスタンスの生成において、<code>IDisposable</code>インターフェースを実装しているので<code>using</code>ブロックで囲うものがある。<br>
<code>HttpClient</code>クラスのインスタンスの生成において、<code>IDisposable</code>インターフェースを実装しているので<code>using</code>ブロックで囲うものがある。<br>
しかし、これは通信を実行するごとにソケットを開くことにより、大量のリソースを消費してリソースが枯渇する場合がある。<br>
<br>
<br>
しかし、これは通信を実行するごとにソケットを開くことにより、大量のリソースを消費してリソースが枯渇する場合がある。<br>
以下の例では、http://aspnetmonsters.com に対して、GETを行う10リクエストを開く。<br>
  <syntaxhighlight lang="c#">
  <syntaxhighlight lang="c#">
  using (var client = new HttpClient())
// アンチパターン
  using System;
using System.Net.Http;
public class Program
  {
  {
     await client.PostAsync("http://iketeru-service.com/");
     public static async Task Main(string[] args)
    {
      for (var i = 0; i < 10; i++)
      {
          using(var client = new HttpClient())
          {
            var result = await client.GetAsync("http://aspnetmonsters.com");
            Console.WriteLine(result.StatusCode);
          }
      }
      Console.WriteLine("Connections done");
    }
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
次に、アプリケーションを終了して、netstatコマンドを実行してPCのソケットの状態を確認する。<br>
<br>
状態は<code>TIME_WAIT</code>であり、WebサイトをホストしているPCへの接続が開かれている状態である。<br>
これは、接続は閉じられているが、ネットワーク上で遅延が発生している可能性があるため、追加のパケットが送られてくるのを待つ状態である。<br>
Proto  Local Address          Foreign Address        State
TCP    10.211.55.6:12050      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12051      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12053      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12054      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12055      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12056      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12057      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12058      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12059      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12060      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12061      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12062      waws-prod-bay-017:http  TIME_WAIT
TCP    127.0.0.1:1695        SIMONTIMMS742B:1696    ESTABLISHED
...略
<br>
==== HttpRequestMessageクラス ====
==== HttpRequestMessageクラス ====
固定のリクエストヘッダや認証情報を付加した<code>HttpRequestMessage</code>クラスを使用する場合、共通の内部メソッドである<code>CreateRequest()</code>を使用する。<br>
固定のリクエストヘッダや認証情報を付加した<code>HttpRequestMessage</code>クラスを使用する場合、共通の内部メソッドである<code>CreateRequest()</code>を使用する。<br>

2024年1月23日 (火) 18:55時点における版

概要

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

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


HttpClientクラスの仕様

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

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

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


アンチパターン

HttpClientクラス

HttpClientクラスのインスタンスの生成において、IDisposableインターフェースを実装しているのでusingブロックで囲うものがある。
しかし、これは通信を実行するごとにソケットを開くことにより、大量のリソースを消費してリソースが枯渇する場合がある。

以下の例では、http://aspnetmonsters.com に対して、GETを行う10リクエストを開く。

 // アンチパターン
 
 using System;
 using System.Net.Http;
 
 public class Program
 {
    public static async Task Main(string[] args) 
    {
       for (var i = 0; i < 10; i++)
       {
          using(var client = new HttpClient())
          {
             var result = await client.GetAsync("http://aspnetmonsters.com");
             Console.WriteLine(result.StatusCode);
          }
       }
 
       Console.WriteLine("Connections done");
    }
 }


次に、アプリケーションを終了して、netstatコマンドを実行してPCのソケットの状態を確認する。

状態はTIME_WAITであり、WebサイトをホストしているPCへの接続が開かれている状態である。
これは、接続は閉じられているが、ネットワーク上で遅延が発生している可能性があるため、追加のパケットが送られてくるのを待つ状態である。

Proto  Local Address          Foreign Address        State
TCP    10.211.55.6:12050      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12051      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12053      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12054      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12055      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12056      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12057      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12058      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12059      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12060      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12061      waws-prod-bay-017:http  TIME_WAIT
TCP    10.211.55.6:12062      waws-prod-bay-017:http  TIME_WAIT
TCP    127.0.0.1:1695         SIMONTIMMS742B:1696    ESTABLISHED

...略


HttpRequestMessageクラス

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

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


Cookieのキャッシュ

Cookieの送受信を行う場合、Cookieがキャッシュされる。
これは、HttpClientクラスのインスタンス生成時において、UseCookiesプロパティをfalseにすることにより回避できる。

もし、プロキシサーバを実装しており、かつ、Cookieを引き継ぐ必要がある場合は、Cookieヘッダを追加する。

 var handler = new HttpClientHandler()
 {
    UseCookies = false,  // false : Cookieをキャッシュしない
                         // true  : Cookieをキャッシュする
 };
 
 var client = new HttpClient(handler);



解決策

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クラスのオブジェクトを生成する方がよい。