(6)ASP.NET Core 中使用IHttpClientFactory發出HTTP請求

1.HttpClient類使用存在的問題

HttpClient類的使用所存在的問題,百度搜索的文章一大堆,好多都是單純文字描述,讓人感受不太好理解,爲了更好理解HttpClient使用存在的問題,下面讓咱們經過代碼跟示例來描述。html

using(var client = new HttpClient())

傳統關閉鏈接方法如上述代碼所示,但當使用using語句釋放HttpClient對象的時候,套接字(socket)也不會當即釋放,下面咱們經過請求aspnetmonsters站點的示例來驗證下:node

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting connections");
        var g = GetAsync();
        g.Wait();
        Console.WriteLine("Connections done");
        Console.ReadKey();
    }
    static async Task GetAsync()
    {
        for (int i = 0; i < 5; i++)
        {
            using (var client = new HttpClient())
            {
                var result = await client.GetAsync("http://aspnetmonsters.com/");
                Console.WriteLine(result.StatusCode);
            }
        }
    }
}

輸出結果:git

控制檯打印出五條請求站點返回狀態的信息,下面咱們經過netstat工具打印出五個請求鏈接套接字狀態:github

 

應用程序已經運行結束了(結束鏈接),可是打印結果顯示鏈接狀態仍然是TIME_WAIT,也就是說在此狀態期間仍然在觀察是否有數據包進入鏈接(若是鏈接等待中有任何數據包仍然會經過),由於它們可能在某個地方被網絡延遲,這是我從tcpstate竊取的TCP / IP狀態圖。json

 

Windows將在此狀態下保持鏈接240秒(由其設置[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])。Windows能夠快速打開新套接字的速度有限,所以若是您耗盡鏈接池,那麼您可能會看到以下錯誤:後端

 

而怎麼作才能夠減小套接字的浪費呢?咱們在上述代碼中把每次循環中建立的HttpClient對象拉到Main外定義爲一個共享的靜態實例:api

class Program
{
    private static HttpClient client = new HttpClient();
    static void Main(string[] args)
    {
        Console.WriteLine("Starting connections");
        var g = GetAsync();
        g.Wait();
        Console.WriteLine("Connections done");
        Console.ReadKey();
    }
    static async Task GetAsync()
    {
        for (int i = 0; i < 5; i++)
        {
            var result = await client.GetAsync("http://aspnetmonsters.com/");
            Console.WriteLine(result.StatusCode);
        }
    }
}

應用程序運動完畢以後,咱們再經過netstat工具打印出五個請求鏈接套接字狀態,這時候會看到信息以下:網絡

 

經過共享一個實例,減小了套接字的浪費,實際上因爲套接字重用而傳輸快一點。
總結:
●在建立HttpClient實例的時候,最好是靜態(static )實例。
●不要用using包裝HttpClient對象。
在.NET Core 2.1版本以後引入的 HttpClientFactory解決了HttpClient的全部痛點。有了 HttpClientFactory,咱們不須要關心如何建立HttpClient,又如何釋放它。經過它能夠建立具備特定業務的HttpClient,並且能夠很友好的和 DI 容器結合使用,更爲靈活。下面以 ASP.NET Core爲例介紹HttpClientFactory的四種使用方式。app

2.HttpClientFactory 的多種使用方式

能夠經過多種使用方式在應用程序中使用HttpClientFactory。socket

2.1直接使用HttpClientFactory

在Startup.ConfigureServices方法中,經過在IServiceCollection上調用AddHttpClient擴展方法能夠註冊IHttpClientFactory服務。
services.AddHttpClient();
註冊服務後,咱們新建BasicUsageModel類使用IHttpClientFactory建立HttpClient實例:

public class BasicUsageModel
{
    private readonly IHttpClientFactory _clientFactory;
    public IEnumerable<GitHubBranch> Branches { get; private set; }
    public bool GetBranchesError { get; private set; }
    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }
    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
        var client = _clientFactory.CreateClient();
        var response = await client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}
public class GitHubBranch
{
    public string name { get; set; }
}

以這種方式直接在使用IHttpClientFactory的類中調用CreateClient方法建立HttpClient實例。而後在Controller中調用BasicUsageModel類:

public class HomeController : Controller
{
    private readonly IHttpClientFactory _clientFactory;
    public HomeController(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }
    public IActionResult Index()
    {
        BasicUsageModel model = new BasicUsageModel(_clientFactory);
        var task = model.OnGet();
        task.Wait();
        List<GitHubBranch> list = model.Branches.ToList();
        return View(list);
    }
}

2.2使用命名客戶端

若是應用程序須要有許多不一樣的HttpClient用法(每種用法的服務配置都不一樣),能夠視狀況使用命名客戶端。能夠在HttpClient中註冊時指定命名Startup.ConfigureServices的配置。

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

上面的代碼調用AddHttpClient,同時提供名稱「github」。此客戶端應用了一些默認配置,也就是須要基址和兩個標頭來使用GitHub API。每次調用CreateClient時,都會建立HttpClient 的新實例,並調用配置操做。要使用命名客戶端,可將字符串參數傳遞到CreateClient。指定要建立的客戶端的名稱:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;
    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
    public bool GetPullRequestsError { get; private set; }
    public bool HasPullRequests => PullRequests.Any();
    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }
    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "repos/aspnet/AspNetCore.Docs/pulls");
        var client = _clientFactory.CreateClient("github");
        var response = await client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}
public class GitHubPullRequest
{
    public string url { get; set; }
    public int? id { get; set; }
    public string node_id { get; set; }
}

在上述代碼中,請求不須要指定主機名。能夠僅傳遞路徑,由於採用了爲客戶端配置的基址。在Controller中調用方法如上個示例。

2.3使用類型化客戶端

什麼是「類型化客戶端」?它只是DefaultHttpClientFactory注入時配置的HttpClient。
下圖顯示瞭如何將類型化客戶端與HttpClientFactory結合使用:

類型化客戶端提供與命名客戶端同樣的功能,不須要將字符串用做密鑰。它們提供單個地址來配置特定HttpClient並與其進行交互。例如,單個類型化客戶端可能用於單個後端終結點,並封裝此終結點的全部處理邏輯。另外一個優點是它們使用 DI 且能夠被注入到應用中須要的位置。
類型化客戶端在構造函數中接收HttpClient參數:

public class GitHubService
{
    public HttpClient Client { get; }
    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");
        Client = client;
    }
    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
        response.EnsureSuccessStatusCode();
        var result = await response.Content
            .ReadAsAsync<IEnumerable<GitHubIssue>>();
        return result;
    }
}
public class GitHubIssue
{
    public string url { get; set; }
    public int? id { get; set; }
    public string node_id { get; set; }
}

在上述代碼中,配置轉移到了類型化客戶端中。HttpClient對象公開爲公共屬性。能夠定義公開HttpClient功能的特定於API的方法。GetAspNetDocsIssues方法從GitHub存儲庫封裝查詢和分析最新待解決問題所需的代碼。
要註冊類型化客戶端,可在Startup.ConfigureServices中使用通用的AddHttpClient擴展方法,指定類型化客戶端類:

services.AddHttpClient<GitHubService>();

使用DI將類型客戶端註冊爲暫時客戶端。能夠直接插入或使用類型化客戶端:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;
    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
    public bool HasIssue => LatestIssues.Any();
    public bool GetIssuesError { get; private set; }
    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }
    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch (HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

 

參考文獻:
在ASP.NET Core中使用IHttpClientFactory發出HTTP請求
你正在以錯誤方式使用 HttpClient,這將致使軟件受損

相關文章
相關標籤/搜索