前回の記事でIHttpClientFactoryの使用方法を説明しました。
ここでは、サンプルを使用した具体的な使用方法を説明します。
前提
- マイクロソフトが推奨するIHttpClientFactoryを使用して、HttpClientクライアントを取得する前提です。
- HttpClientの使い方は何種類かありますが、ここでは既定のクライアント、名前付きクライアントを前提とします。
実際の業務では、名前付き/型付きクライアントを使う場合が多いと思われますが、HttpClientとしての使用法は大きく変わらないため。 - サンプルはWindows10 + Visual Studio 2019(ASP.NET Core 3.1)環境で確認しています。
HttpClientの基本的な使い方
サービスにクライアントを登録・構成し、
必要なタイミングでクライアントを取得してHTTP要求を実行する流れになります。
サービスへのクライアントの追加と構成
次のようにStartup.csでAddHttpClient()を使ってクライアントを登録します。
下記の例のようにクライアントの構成を行う他に、独自に要求/応答をカスタマイズするためのハンドラやエラー処理をカスタマイズするためのPollyポリシーを設定することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 | public void ConfigureServices(IServiceCollection services) { ... services.AddHttpClient(); // 既定のクライアントを登録する場合 services.AddHttpClient("name"); // 名前付きクライアントを登録する場合 services.AddHttpClient<MyHttpClient>(); // 型付きクライアントを登録する場合 // 名前付きクライアントを登録・構成する場合 services.AddHttpClient("name2", options => { options.BaseAddress = new Uri("https://..."); options.DefaultRequestHeaders.Add("key", "value"); }); |
HTTP要求の実行
自由度の高いHTTP要求を作成できるSendAsync()を使うか、
より簡潔にHTTP要求を作成できるGetAsync()/PostAsync()等の便利なメソッドを使用する方法があります。
汎用的な方法: SendAsync()の使用
HTTP要求を表現するHttpRequestMessageオブジェクトを作成し、HTTPメソッドやURIの指定、必要に応じてHTTPヘッダやコンテンツ(ボディ)を指定します。
このオブジェクトを引数として、HttpClientのSendAsync()を実行します。HTTP応答はHttpResponseMessageに格納され、ステータスコードの確認、必要に応じてHTTPヘッダやコンテンツを取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class XXXController : Controller { private readonly IHttpClientFactory _factory; public XXXController(IHttpClientFactory factory) { _factory = factory; } public async Task<IActionResult> TestPost() { // HTTP要求の作成 var request = new HttpRequestMessage(HttpMethod.Post, "https://..."); request.Headers.Add("KEY", "value"); request.Content = new StringContent("hello world!"); // HTTP要求の送信 HttpClient client = _factory.CreateClient(); HttpResponseMessage response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { // 正常時の処理 string body = await response.Content.ReadAsStringAsync(); ... }else{ // エラー時の処理 } |
- 既定のクライアントや名前付きクライアントの場合、コンストラクタで定義したIHttpClientFactoryからクライアントを取得する必要があります。(コンストラクタでIHttpClientFactoryを引数宣言するとDependency Injectionの仕組みでインスタンスが設定されます。)
- HttpRequestMessageでは、HeadersプロパティでHTTPヘッダの編集、Contentプロパティでコンテンツ(HTTPボディ相当)を設定ができます。
- ContentプロパティはHttpContent型となっており、このクラスを継承した次のコンテンツを指定できます。詳細はリファレンスを参照のこと。
コンテンツ(クラス) 説明 StringContent テキスト形式のコンテンツ
Content-Typeの既定は”text/plain; charset=utf-8″であり、コンストラクタの引数やHeadersプロパティから指定可。StreamContent
ByteArrayContentストリーム型・バイト配列型のコンテンツ
既定のContent-Typeが指定されないので、Headers.ContentTypeプロパティやHttpRequestMessageで指定することになります。FormUrlEncodedContent フォーム形式のコンテンツ
input要素で入力した想定の形式です。
Content-Typeは”application/x-www-form-urlencoded”になります。MultipartFormDataContent マルチパートフォーム形式のコンテンツ
input要素で入力した想定の形式で、ファイルアップロードの場合に使用する。
Content-Typeは”multipart/form-data; boundary=…”になります。MultipartContent 任意のマルチパート形式のコンテンツ
Webアプリでマルチパート形式を使用する場合、上記の”multipart/form-data”になるので、このコンテンツを使用することはないと思います。MultipartFormDataContentの基底クラスです。
簡潔な方法: GetAync(), PostAsync()等の使用
StringContent等のコンテンツを使ってHTTP要求を実行できます。
HttpRequestMessageオブジェクトを作成する必要がなく、前述の汎用的な方法より簡潔に実装できます。
この方法では、HTTPメソッドに対応するGetAsync(), PostAsync(), PutAsync(), DeleteAsync()が提供されています。これらのメソッドに対して、URIとコンテンツを引数してHTTP要求を実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class XXXController : Controller { private readonly IHttpClientFactory _factory; public XXXController(IHttpClientFactory factory) { _factory = factory; } public async Task<IActionResult> TestPostSimple() { // HTTP要求(HttpContent)の作成 var content = new StringContent("hello world!"); content.Headers.Add("KEY", "value"); // HTTP要求の送信 HttpClient client = _factory.CreateClient(); HttpResponseMessage response = await client.PostAsync("https://...", content); if (response.IsSuccessStatusCode) { // 正常時の処理 string body = await response.Content.ReadAsStringAsync(); ... }else{ // エラー時の処理 } |
参考
- 常時付与するHTTPヘッダがある場合、Startup時のAddHttpClientオプション(HttpClient.DefaultRequestHeaders)で指定することをお薦めします。
- 業務アプリの開発では、開発環境と本番環境等のように実行環境に応じて、ベースURIを切り替えられるようにしたい場合があります。Startup時のAddHttpClientオプション(HttpClient.BaseAddress)で”https://xxxx/”等のベースのURIを指定し、個別のHttpClientの呼び出しでは”/api/xxx”等の相対パスを指定するような実装をお薦めします。
サンプル
ここではPOST/GETを使った基本的なサンプルを説明します。
このサンプルでは、ASP.NET Core MVCのコントローラからIHttpClientFactoryを使って、ローカルのAPIコントローラにHTTP要求を発行しています。
各サンプルでは、次のように名前付きクライアントとして登録した”basic”を使用しています。
1 2 3 4 5 6 7 8 9 10 | public void ConfigureServices(IServiceCollection services) { ... services.AddHttpClient("basic", options => { options.BaseAddress = new Uri("https://localhost:44399"); options.DefaultRequestHeaders.Add("X-ACCESS-KEY", "secret"); options.DefaultRequestHeaders.Accept.Clear(); options.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); }); |
サンプルのプロジェクトや完全なソースコードは下記をご覧ください
Contribute to nextdoorwith/example-aspdotnet3 development by…
クエリ文字列を指定してGET実行
クエリ文字列の作成は、.NET Core 3系から標準で含まれるWebUtilitiesのQueryHelpersを使用できます。
HTTP要求の送信はHttpClientのGetAsync()で行います。DELETEを実行する場合はDeleteAsync()を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public async Task<IActionResult> PostBasicGet() { var queries = new Dictionary<string, string>(); queries.Add("arg1", "abcxyz!#$%&_=-003"); queries.Add("arg2", "あいうえお"); string uri = QueryHelpers.AddQueryString("/api/SampleApi", queries); HttpClient client = _factory.CreateClient("basic"); HttpResponseMessage response = await client.GetAsync(uri); if( response.IsSuccessStatusCode) { // ... } |
生成されるHTTP要求は次のようになります。
(ログに出力されるURLを見るとarg1はURLエンコードあり、arg2はURLエンコードなしに見えますが、実際にはどちらもURLエンコードされていました。)
1 2 3 4 5 | GET /api/SampleApi?arg1=abcxyz!%23$%25%26_%3D-003&arg2=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A ... Host: ... X-ACCESS-KEY: secret Accept: application/json Request-Id: ... |
HTTPヘッダを指定してGET実行
上記の方法だとクライアント登録時の構成でしかHTTPヘッダを指定できません。
このような場合は汎用的な方法を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public async Task<IActionResult> PostGenericGet() { // HTTP要求の作成 var request = new HttpRequestMessage(HttpMethod.Get, "/api/SampleApi"); request.Headers.Add("X-ARG1", "abcxyz!#$%&_=-003"); request.Headers.Add("X-ARG2", "あいうえお"); // HTTP要求の送信 HttpClient client = _factory.CreateClient("basic"); HttpResponseMessage response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { // ... } |
生成されるHTTP要求は次のようになります。
1 2 3 4 5 6 | GET /api/SampleApi HTTP/1.1 Host: ... X-ARG1: abcxyz!#$%&_=-003 X-ACCESS-KEY: secret Accept: application/json Request-Id: ... |
フォーム形式によるPOST実行
FormUrlEncodedContentオブジェクトにキーと値を格納し、HTTP要求を発行します。
Content-Typeは”application/x-www-form-urlencoded”になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public async Task<IActionResult> PostBasicPost() { var values = new Dictionary<string, string>(); values.Add("arg1", "abcxyz!#$%&_=-003"); values.Add("arg2", "あいうえお"); var content = new FormUrlEncodedContent(values); HttpClient client = _factory.CreateClient("basic"); HttpResponseMessage response = await client.PostAsync("/api/SampleApi/post-only", content); if (response.IsSuccessStatusCode) { // ... } |
生成されるHTTP要求は次のようになります。
1 2 3 4 5 6 7 8 9 | POST /api/SampleApi/post-only HTTP/1.1 Host: ... X-ACCESS-KEY: secret Accept: application/json Request-Id: ... Content-Type: application/x-www-form-urlencoded Content-Length: 85 arg1=abcxyz%21%23%24%25%26_%3D-003&arg2=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A |
マルチパートフォーム形式によるPOST実行
MultipartFormDataContentオブジェクトにキーと値を格納し、HTTP要求を発行します。
値として、アップロードするファイル内容を表現するストリームやバイト配列を指定できます。
Content-Typeは”multipart/form-data”になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public async Task<IActionResult> PostBasicPostMultipart() { // サンプルとして添付するストリーム var bin = new byte[] {1, 2, 3, 4, 5, 6, 7, 8}; var stream = new MemoryStream(bin); var content = new MultipartFormDataContent(); // マルチパート: arg1 content.Add(new StringContent("abcxyz!#$%&_=-003"), "arg1"); // マルチパート: arg2 content.Add(new StringContent("あいうえお"), "arg2"); // マルチパート: arg3 var streamContent = new StreamContent(stream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = "添付ファイル1.dat", // エンコードしたファイル名(RFC5987) FileName = "attached-file1.dat" // ファイル名(FileNameStarが優先) }; content.Add(streamContent, "arg3"); HttpClient client = _factory.CreateClient("basic"); HttpResponseMessage response = await client.PostAsync("/api/SampleApi/post-only", content); if (response.IsSuccessStatusCode) { // ... } |
生成されるHTTP要求は次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | POST /api/SampleApi/post-only HTTP/1.1 Host: ... X-ACCESS-KEY: secret Accept: application/json Request-Id: ... Content-Type: multipart/form-data; boundary="c520cbd6-7865-4910-8495-82f139e207c2" Content-Length: 562 --c520cbd6-7865-4910-8495-82f139e207c2 Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=arg1 abcxyz!#$%&_=-003 --c520cbd6-7865-4910-8495-82f139e207c2 Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=arg2 あいうえお --c520cbd6-7865-4910-8495-82f139e207c2 Content-Type: application/octet-stream Content-Disposition: attachment; filename*=utf-8''%E6%B7...%83%AB1.dat; filename=attached-file1.dat (バイナリデータ) --c520cbd6-7865-4910-8495-82f139e207c2-- |
プロキシの使用
HttpClientが実際の通信を行う際、内部でメッセージハンドラを使用しています。
.NET Core 2.1以降では、既定のメッセージハンドラとしてSocketsHttpHandlerが使用されており、このプロパティでプロキシを指定できます。
詳細はSocketsHttpHandlerのリファレンスを参照のことですが、プロキシ以外にもCookieやコネクションタイムアウト等の細かい設定が可能です。.NET Core 2.1より前の既定のメッセージハンドラはHttpClientHandlerでした。HttpClientHandlerに比べ、SocketsHttpHandlerはより高性能でプラットフォーム非依存とのことです。
1 2 3 4 5 6 7 8 9 10 11 | public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("proxy") .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { UseProxy = true, Proxy = new WebProxy("localhost", 8888) } ); |
1 2 3 4 5 6 7 8 | public async Task<IActionResult> DoGetViaProxy() { HttpClient client = _factory.CreateClient("proxy"); HttpResponseMessage response = await client.GetAsync("https://www.yahoo.co.jp"); if (response.IsSuccessStatusCode) { // ... } |
ローカルで検証用に構築したプロキシサーバ(Apache HTTPサーバ)を使って動作を確認します。
このプロキシサーバの構築についてはこちらをご覧ください。
出だしだけですが、生成されるHTTP要求の例は次のようになります。
1 2 | CONNECT www.yahoo.co.jp:443 HTTP/1.1 Host: www.yahoo.co.jp:443 |