問題來源
長期以來,.NET開發者都通過下面的方式發送http請求:
using (var httpClient = new HttpClient())
{
var response = await httpClient.GetAsync(uri);
//do something with response
}
這段代碼理論上來說遵守了C#的最佳實踐,HttpClient是IDisposable類型,所以我們通過using語法糖來使用HttpClient。微軟官方的文檔也提到:
As a rule, when you use an IDisposable object, you should declare and instantiate it in a using statement
可是,當我們試圖運行下面的測試:
public async Task SendRequest()
{
Console.WriteLine("Starting reqeust");
for(int i = 0; i<10; i++)
{
using(var client = new HttpClient())
{
var result = await client.GetAsync("http://www.baidu.com");
Console.WriteLine(result.StatusCode);
}
}
Console.WriteLine("Reqeust done");
}
此時在terminal下列出所有端口:
netstat -ap tcp | grep -i "time_wait"

for(int i = 0; i<10; i++)
{
var result = await _client.GetAsync("http://www.baidu.com");
Console.WriteLine(result.StatusCode);
}
_testOutputHelper.WriteLine("Request done");
}
這個方案似乎解決了問題,使用單例的HttpClient的確會減少Socket資源,但是這個方案會引發新的問題:由於這個Http連接始終保持連接狀態,所以當請求地址的DNS發生更新的時候並不會應用到這個Http連接上。這個問題在微服務,高可用時代更加常見[Singeton HttpClient doesn't respect DNS changes](https://github.com/dotnet/corefx/issues/11224)。
最終,一個叫做[HttpClientFactory](https://github.com/aspnet/HttpClientFactory)的開源實現用來徹底解決這個問題。微軟也將HttpClientFactory集成在了.NET Core中。
##在.NET Core中創建HttpClient
#### 1.添加Nuget
Microsoft.Extensions.Http
#### 2.在Dependency Injection容器中注冊服務
```csharp
services.AddHttpClient();
3. 使用構造器注入使用IhttpClientFactory
public class BasicUsage
{
private readonly IHttpClientFactory _clientFactory;
public BasicUsage(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task SendRequest()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"http://www.baidu.com");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
//do something for response
}
}
4. 使用Named HttpClient
由於我們在DI容器中注冊了唯一的HttpClientFactory,意味着通過HttpClientFactory創建出來的HttpClient可能是同一個配置和參數,如果你需要不同配置的HttpClient,你可以通過“起名字的”的方式注冊不同的HttpClient。
services.AddHttpClient("baidu", c =>
{
c.BaseAddress = new Uri("https://www.baidu.com");
c.DefaultRequestHeaders.Add("Accept", "application/json");
});
一旦注冊了一個名叫“baidu"的HttpClient,你就可以通過下面的方式來建創建HttpClient:
var client = _clientFactory.CreateClient("baidu");
5.集成Polly
Polly是一個用來故障處理庫,它允許開發者在Http請求中添加“重試、熔斷器、超時等”策略。
先添加Nuget:
Microsoft.Extensions.Http.Polly
添加策略:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));
services.AddHttpClient("baidu")
.AddPolicyHandler(request => timeout)
.AddTransientHttpErrorPolicy(p=>p.RetryAsync(3));
當然還有一些高階用法,詳見Initiate HTTP requests,總之HttpClientFactory提供了一種高效實用HttpClient的方式,如果你還在自己new HttpClient,請趕快試試新的方案吧。