HttpClientFactory 是 HttpClient 的正確使用方式


1.簡介

  • 它的組件包是Microsoft.Extensions.Http
  • 復原HttpClient帶來的問題

HttpClient相關問題

  • 雖然HttpClient類實現了IDisposable,但不是首選在using語句中聲明和實例化它,因為釋放HttpClient對象時,基礎套接字不會立即釋放,這可能會導致“套接字耗盡”問題 。因此,HttpClient 應進行一次實例化並在應用程序的生命周期中重復使用。 在負載較重的情況下,實例化每個請求的 HttpClient 類將耗盡可用的套接字數。 該問題會導致 SocketException 錯誤。 要解決此問題,可能的方法是將 HttpClient 對象創建為單一對象或靜態對象,在長期運行的進程中使用 HttpClient 的共享實例時,開發人員遇到的另一個問題。 在將 HttpClient 實例化為單一實例或靜態對象的情況下,它無法處理 DNS 更改,但是,問題實際上不是 HttpClient 本身,而是 HttpClient 的默認構造函數,因為它創建了一個新的實際 HttpMessageHandler 實例,該實例具有上面提到的“套接字耗盡”和 DNS 更改問題 。

參考解決內容

核心點

  • 管理內部HttpMessageHandler的生命周期,靈活應對資源問題和DNS刷新問題。
  • 支持命名化,類型化配置,集中管理配置,避免沖突
  • 靈活的出站請求管道配置,輕松管理請求生命周期
  • 內置管道最外層和最內存日志記錄器,有Information和Trace輸出

核心對象

  • HttpClient
  • HttpMessageHandler
  • SocketsHttpHandler
  • DelegatingHandler
  • IHttpClientFactory
  • IHttpClientBuilder

2.使用方式

  • 基本用法
  • 命名客戶端模式
  • 類型化客戶端模式
  • 生成的客戶端

基本用法

//首先,在Startup中的ConfigureService注冊我們的HttClient服務,僅僅一行代碼就可以搞定
service.AddHttpClien();

//接着,我們通過依賴注入來請求IHttpClientFactory,並創建HttpClient實例,以下代碼之后我們就可以放心使用HttpClient
private readonly IHttpClientFactory _clientFactory;
public BasicUsageModel(IHttpClientFactory clientFactory)
{
  _clientFactory = clientFactory;
}
public async Task OnGet()
{
    var client = _clientFactory.CreateClient();
        client.BaseAddress = new Uri("http://api.github.com");
        string result = await client.GetStringAsync("/");
        return Ok(result);
    
}

命名式使用方式

命名式的使用方式可以解決第一種方式的問題,對於特定域名的 HTTP 調用,可以只做一次配置,然后在多個地方通過名稱拿到相同的 Client 來使用。

首先在 Startup.cs 注冊服務的地方添加帶有特定名稱的 HttpClient 服務,並添加需要的配置,如下示例:

services.AddHttpClient();
services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

注意這里添加了兩次 AddHttpClient,一次是沒有命名的,一次是有命名的。在 Controller 中的使用方式是:

public class ValuesController : BaseController
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient("github");
        string result = await client.GetStringAsync("/");
        return Ok(result);
    }
}

但如果使用很頻繁,這種獲取 Client 的方式還是有一點麻煩。下面介紹第三種方式,可以在使用的時候方便一點,但配置要麻煩些。

類型化客戶端

public class GitHubClient
{
    public HttpClient Client { get; private set; }

    public GitHubClient(HttpClient httpClient)
    {
        httpClient.BaseAddress = new Uri("https://api.github.com/");
        httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
        Client = httpClient;
    }

    public async Task<string> GetData()
    {
        return await _client.GetStringAsync("/");
    }
}

//注冊
services.AddHttpClient<GitHubClient>();

然后在需要的地方(比如 Controller)中這樣使用:

public class ValuesController : BaseController
{
    private readonly GitHubClient _gitHubClient;

    public ValuesController(GitHubClient gitHubClient)
    {
        _gitHubClient = gitHubClient;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        string result = await _gitHubClient.GetData();
        return Ok(result);
    }
}

類型化的方式和命名式的方式相比,雖然在配置的時候會麻煩一點,但它有兩個好處:一是不用通過名稱字符串來獲得 Client,而是直接通過依賴注入獲得具體的 Client;二是可以在自定義的 HttpClient 類中擴展共用的方法。

3.出站請求中間件

  • HttpClient具有委托處理程序概念。

創建委托處理程序

  • 派生自DelegatingHandler (將 HTTP 響應消息的處理委托給另一處理程序(稱為“內部處理程序”)的 HTTP 處理程序的類型。)
  • 重寫SendAsync
 public class xxxHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //處理請求
           
            var result = await base.SendAsync(request, cancellationToken); //調用內部handler

            //處理響應

            return result;
        }
    }
      
 public void ConfigureServices(IServiceCollection services){
             services.AddSingleton<xxxHandler>();  //創建xxxHandler服務生命周期
            services.AddHttpClient("XXXServiceClient", client =>
            {
                client.DefaultRequestHeaders.Add("client-name", "namedclient");
                client.BaseAddress = new Uri("https://localhost:5001");
            }).SetHandlerLifetime(TimeSpan.FromMinutes(20)) //SetHandlerLifetime設置生命周期時長,刷新DNS時間
            .AddHttpMessageHandler(provider => provider.GetService<xxxHandler>()); //這里添加 Handler
}
public class XXXServiceClient
    {
        IHttpClientFactory _httpClientFactory;

        const string _clientName = "NamedOrderServiceClient";  //定義客戶端名稱

        public XXXServiceClient(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }
        public async Task<string> Get()
        {
            var client = _httpClientFactory.CreateClient(_clientName); //使用客戶端名稱獲取客戶端

            //使用client發起HTTP請求,這里使用相對路徑來訪問
            return await client.GetStringAsync("/OrderService");
        }
    }

生命周期

管道模式

本圖來自極客時間-肖偉宇

  • 每次對 IHttpClientFactory 調用 CreateClient 都會返回一個新 HttpClient 實例。 每個命名客戶端都創建一個 HttpMessageHandler。 工廠管理 HttpMessageHandler 實例的生存期。
  • 處理程序的默認生存期為兩分鍾,可通過SetHandlerLifetime 重新設置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM