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
能够经过多种使用方式在应用程序中使用HttpClientFactory。socket
在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); } }
若是应用程序须要有许多不一样的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中调用方法如上个示例。
什么是“类型化客户端”?它只是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,这将致使软件受损