ASP.NET Core 2.2 基礎知識(十二) 發送 HTTP 請求
可以註冊 IHttpClientFactory 並將其用於配置和創建應用中的 HttpClient 實例。 這能帶來以下好處:
- 提供一個中心位置,用於命名和配置邏輯
HttpClient
實例。 例如,可以註冊 github 客戶端,並將它配置為訪問 GitHub。 可以註冊一個默認客戶端用於其他用途。 - 通過委托
HttpClient
中的處理程序整理出站中間件的概念,並提供適用於基於 Polly 的中間件的擴展來利用概念。 - 管理基礎
HttpClientMessageHandler
實例的池和生存期,避免在手動管理HttpClient
生存期時出現常見的 DNS 問題。 - (通過
ILogger
在應用中可以通過以下多種方式使用 IHttpClientFactory
基本用法
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddHttpClient(); }
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private readonly IHttpClientFactory _clientFactory; public ValuesController(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } // GET api/values[HttpGet] public async Task<string> Get() { HttpClient client = _clientFactory.CreateClient(); //方法一: //HttpRequestMessage request = new HttpRequestMessage //{ // Method = new HttpMethod("get"), // RequestUri = new System.Uri("http://localhost:5000/api/values"), //}; //HttpResponseMessage response = await client.SendAsync(request); //string res = await response.Content.ReadAsStringAsync(); //return res; //方法二: string res = await client.GetStringAsync("http://localhost:5000/api/values"); return res; } }
命名客戶端
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddHttpClient("test", c => { c.BaseAddress = new Uri("http://localhost:5000"); }); }
public async Task<string> Get() { HttpClient client = _clientFactory.CreateClient("test"); //註冊名叫 "test" 的客戶端時,已經指定了該客戶端的請求基地址,所以這裏不需要指定主機名了 return await client.GetStringAsync("api/values"); }
類型化客戶端
public class TestHttpClient { public HttpClient Client { get; set; } public TestHttpClient(HttpClient client) { client.BaseAddress = new System.Uri("http://localhost:5000"); Client = client; } public async Task<string> Get() { return await Client.GetStringAsync("api/values"); } }
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddHttpClient<TestHttpClient>(c => { //可以在這裏設置,也可以在構造函數設置. //c.BaseAddress = new System.Uri("http://localhost:5000"); }); }
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private readonly TestHttpClient _client; public ValuesController(TestHttpClient client) { _client = client; } [HttpGet] public async Task<string> Get() { return await _client.Get(); } }
出站請求中間件
HttpClient
已經具有委托處理程序的概念,這些委托處理程序可以鏈接在一起,處理出站 HTTP 請求。 IHttpClientFactory
可以輕松定義處理程序並應用於每個命名客戶端。 它支持註冊和鏈接多個處理程序,以生成出站請求中間件管道。 每個處理程序都可以在出站請求前後執行工作。 此模式類似於 ASP.NET Core 中的入站中間件管道。 它提供了一種用於管理圍繞 HTTP 請求的橫切關註點的機制,包括緩存、錯誤處理、序列化以及日誌記錄。
要創建處理程序,需要定義一個派生自 DelegatingHandler
的類。 重寫 SendAsync
方法,在將請求傳遞至管道中的下一個處理程序之前執行代碼:
public class ValidateHeaderHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (!request.Headers.Contains("refuge")) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("not found refuge") }; } return await base.SendAsync(request, cancellationToken); } }
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddTransient<ValidateHeaderHandler>();//生存期必須是臨時 services.AddHttpClient("test", c => { c.BaseAddress = new Uri("http://localhost:5000"); }) .AddHttpMessageHandler<ValidateHeaderHandler>(); }
HttpClient 和生存期管理
每次對 IHttpClientFactory
調用 CreateClient
都會返回一個新 HttpClient
實例:
public IEnumerable<int> Get() { //測試生存期 for (int i = 0; i < 4; i++) { HttpClient client = i % 2 == 0 ? _clientFactory.CreateClient("test") : _clientFactory.CreateClient(); yield return client.GetHashCode(); } }
CreateClient 方法內部會調用 CreateHandler 方法,後者創建 HttpMessageHandler,
源碼如下:
public HttpClient CreateClient(string name) { if (name == null) throw new ArgumentNullException(nameof (name)); HttpClient httpClient = new HttpClient(this.CreateHandler(name), false); HttpClientFactoryOptions clientFactoryOptions = this._optionsMonitor.Get(name); for (int index = 0; index < clientFactoryOptions.HttpClientActions.Count; ++index) clientFactoryOptions.HttpClientActions[index](httpClient); return httpClient; }
public HttpMessageHandler CreateHandler(string name) { if (name == null) throw new ArgumentNullException(nameof (name)); ActiveHandlerTrackingEntry entry = this._activeHandlers.GetOrAdd(name, this._entryFactory).Value; this.StartHandlerEntryTimer(entry); return (HttpMessageHandler) entry.Handler; }
而這個 _activeHandlers 的類型是 :
一個線程安全的鍵值對集合.
因此,實際上創建的 HttpMessageHandler 實例會匯集到池中.新建 HttpClient
實例時,可能會重用池中的 HttpMessageHandler
實例(如果生存期尚未到期的話).
由於每個處理程序通常管理自己的基礎 HTTP 連接,因此需要池化處理程序.創建超出必要數量的處理程序可能會導致連接延遲. 部分處理程序還保持連接無期限地打開,這樣可以防止處理程序對 DNS 更改作出反應.
處理程序的默認生存期為兩分鐘,可在每個命名客戶端上重寫默認值:
services.AddHttpClient("test").SetHandlerLifetime(TimeSpan.FromMinutes(5));
配置 HttpMessageHandler
有時候,我們需要控制客戶端使用的內部 HttpMessageHandler
.
services.AddHttpClient("test") .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { AllowAutoRedirect = false, });
ASP.NET Core 2.2 基礎知識(十二) 發送 HTTP 請求