ASP.NETCore發(fā)出HTTP請求

一酥筝、 類使用存在的問題

HttpClient類的使用所存在的問題脱篙,百度搜索的文章一大堆狰腌,好多都是單純文字描述僧鲁,讓人感覺不太好理解虐呻,為了更好理解HttpClient使用存在的問題,下面讓我們通過代碼跟示例來描述寞秃。

using(var client = new HttpClient())

傳統(tǒng)關(guān)閉連接方法如上述代碼所示斟叼,但當使用using語句釋放HttpClient對象的時候,套接字(socket)也不會立即釋放春寿,下面我們通過請求aspnetmonsters站點的示例來驗證下:

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);

? ? ? ? ? ?}

? ? ? ?}

? ?}

}

輸出結(jié)果:

請輸入圖片描述

控制臺打印出五條請求站點返回狀態(tài)的信息朗涩,下面我們通過netstat工具打印出五個請求連接套接字狀態(tài):

請輸入圖片描述

應用程序已經(jīng)運行結(jié)束了(結(jié)束連接),但是打印結(jié)果顯示連接狀態(tài)仍然是TIME_WAIT堂淡,也就是說在此狀態(tài)期間仍然在觀察是否有數(shù)據(jù)包進入連接(如果連接等待中有任何數(shù)據(jù)包仍然會通過)馋缅,因為它們可能在某個地方被網(wǎng)絡(luò)延遲,這是我從tcpstate竊取的TCP / IP狀態(tài)圖绢淀。

請輸入圖片描述

Windows將在此狀態(tài)下保持連接240秒(由其設(shè)置[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])萤悴。Windows可以快速打開新套接字的速度有限,因此如果您耗盡連接池皆的,那么您可能會看到如下錯誤:

請輸入圖片描述

而怎么做才可以減少套接字的浪費呢覆履?我們在上述代碼中把每次循環(huán)中創(chuàng)建的HttpClient對象拉到Main外定義為一個共享的靜態(tài)實例:

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工具打印出五個請求連接套接字狀態(tài)费薄,這時候會看到信息如下:

請輸入圖片描述

通過共享一個實例硝全,減少了套接字的浪費,實際上由于套接字重用而傳輸快一點楞抡。

總結(jié):

在創(chuàng)建HttpClient實例的時候伟众,最好是靜態(tài)(static )實例。

不要用using包裝HttpClient對象召廷。

在.NET Core 2.1版本之后引入的 HttpClientFactory解決了HttpClient的所有痛點凳厢。有了 HttpClientFactory账胧,我們不需要關(guān)心如何創(chuàng)建HttpClient,又如何釋放它先紫。

通過它可以創(chuàng)建具有特定業(yè)務的HttpClient治泥,而且可以很友好的和 DI 容器結(jié)合使用,更為靈活遮精。下面以 ASP.NET Core為例介紹HttpClientFactory的四種使用方式居夹。

二、HttpClientFactory 的多種使用方式

可以通過多種使用方式在應用程序中使用HttpClientFactory本冲。

2.1准脂、直接使用HttpClientFactory

在Startup.ConfigureServices方法中,通過在IServiceCollection上調(diào)用AddHttpClient擴展方法可以注冊IHttpClientFactory服務檬洞。

services.AddHttpClient();

注冊服務后意狠,我們新建BasicUsageModel類使用IHttpClientFactory創(chuàng)建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的類中調(diào)用CreateClient方法創(chuàng)建HttpClient實例。然后在Controller中調(diào)用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");

});

上面的代碼調(diào)用AddHttpClient,同時提供名稱“github”遮晚。此客戶端應用了一些默認配置性昭,也就是需要基址和兩個標頭來使用GitHub API。

每次調(diào)用CreateClient時县遣,都會創(chuàng)建HttpClient 的新實例糜颠,并調(diào)用配置操作。

要使用命名客戶端萧求,可將字符串參數(shù)傳遞到CreateClient其兴。指定要創(chuàng)建的客戶端的名稱:

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中調(diào)用方法如上個示例匀归。

2.3、使用類型化客戶端

什么是“類型化客戶端”耗帕?它只是DefaultHttpClientFactory注入時配置的HttpClient穆端。

下圖顯示了如何將類型化客戶端與HttpClientFactory結(jié)合使用:

請輸入圖片描述

類型化客戶端提供與命名客戶端一樣的功能,不需要將字符串用作密鑰仿便。它們提供單個地址來配置特定HttpClient并與其進行交互体啰。例如攒巍,單個類型化客戶端可能用于單個后端終結(jié)點,并封裝此終結(jié)點的所有處理邏輯狡赐。另一個優(yōu)勢是它們使用 DI 且可以被注入到應用中需要的位置窑业。

類型化客戶端在構(gòu)造函數(shù)中接收HttpClient參數(shù):

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; }

}

在上述代碼中,配置轉(zhuǎn)移到了類型化客戶端中枕屉。HttpClient對象公開為公共屬性常柄。可以定義公開HttpClient功能的特定于API的方法搀擂。

GetAspNetDocsIssues方法從GitHub存儲庫封裝查詢和分析最新待解決問題所需的代碼西潘。

要注冊類型化客戶端,可在Startup.ConfigureServices中使用通用的AddHttpClient擴展方法哨颂,指定類型化客戶端類:

services.AddHttpClient();

使用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>();

? ? ? ?}

? ?}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末威恼,一起剝皮案震驚了整個濱河市品姓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌箫措,老刑警劉巖腹备,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異斤蔓,居然都是意外死亡植酥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門弦牡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來友驮,“玉大人,你說我怎么就攤上這事驾锰⌒读簦” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵椭豫,是天一觀的道長艾猜。 經(jīng)常有香客問我,道長捻悯,這世上最難降的妖魔是什么匆赃? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮今缚,結(jié)果婚禮上算柳,老公的妹妹穿的比我還像新娘。我一直安慰自己姓言,他們只是感情好瞬项,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布蔗蹋。 她就那樣靜靜地躺著,像睡著了一般囱淋。 火紅的嫁衣襯著肌膚如雪猪杭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天妥衣,我揣著相機與錄音皂吮,去河邊找鬼。 笑死税手,一個胖子當著我的面吹牛蜂筹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芦倒,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼艺挪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兵扬?” 一聲冷哼從身側(cè)響起麻裳,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎器钟,沒想到半個月后掂器,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡俱箱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灭必。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狞谱。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖禁漓,靈堂內(nèi)的尸體忽然破棺而出跟衅,到底是詐尸還是另有隱情,我是刑警寧澤播歼,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布伶跷,位于F島的核電站,受9級特大地震影響秘狞,放射性物質(zhì)發(fā)生泄漏叭莫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一烁试、第九天 我趴在偏房一處隱蔽的房頂上張望雇初。 院中可真熱鬧,春花似錦减响、人聲如沸靖诗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刊橘。三九已至鄙才,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間促绵,已是汗流浹背攒庵。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绞愚,地道東北人叙甸。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像位衩,于是被迫代替她去往敵國和親裆蒸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容