「C Sharpとネットワーク - HttpClient」の版間の差分
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> | ||
以下の例では、http://aspnetmonsters.com に対して、GETを行う10リクエストを開く。<br> | |||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
using | // アンチパターン | ||
using System; | |||
using System.Net.Http; | |||
public class Program | |||
{ | { | ||
await client. | 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
クラスのオブジェクトを生成する方がよい。