1.HttpClient 类使用存在的问题
HttpClient 类的使用所存在的问题, 百度搜索的文章一大堆, 好多都是单纯文字描述, 让人感觉不太好理解, 为了更好理解 HttpClient 使用存在的问题, 下面让我们通过代码跟示例来描述.
using(var client = new HttpClient())
传统关闭连接方法如上述代码所示, 但当使用 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);
- }
- }
- }
- }
输出结果:
控制台打印出五条请求站点返回状态的信息, 下面我们通过 netstat 工具打印出五个请求连接套接字状态:
应用程序已经运行结束了(结束连接), 但是打印结果显示连接状态仍然是 TIME_WAIT, 也就是说在此状态期间仍然在观察是否有数据包进入连接(如果连接等待中有任何数据包仍然会通过), 因为它们可能在某个地方被网络延迟, 这是我从窃取的 TCP / IP 状态图.
Windows 将在此状态下保持连接 240 秒(由其设置[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay]).Windows 可以快速打开新套接字的速度有限, 因此如果您耗尽连接池, 那么您可能会看到如下错误:
而怎么做才可以减少套接字的浪费呢? 我们在上述代码中把每次循环中创建的 HttpClient 对象拉到 Main 外定义为一个共享的静态实例:
- 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 的四种使用方式.
2.HttpClientFactory 的多种使用方式
可以通过多种使用方式在应用程序中使用 HttpClientFactory.
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, 这将导致软件受损
来源: https://www.cnblogs.com/wzk153/p/10945313.html