Asp.Net Core 轻松学-HttpClient的演进和避坑

前言

????在 Asp.Net Core 1.0 时代,由于设计上的问题, HttpClient 给开发者带来了无尽的困扰,用 Asp.Net Core 开发团队的话来说就是:我们注意到,HttpClient 被很多开发人员不正确的使用。得益于 .Net Core 不断的版本快速升级;解决方案也一一浮出水面,本文尝试从各个业务场景去剖析 HttpClient 的各种使用方式,从而在开发中正确的使用 HttpClient 进行网络请求。

1.0时代发生的事情

1.1 在 1.0 时代,部署在 Linux 上的 Asp.Net Core 应用程序进程出现 “套接字资源耗尽” 的异常,该异常通常是由于不停的创建 HttpClient 的实例后产生,因为每创建一个新的 HttpClient 对象,将会消耗一个套接字资源,而在对象使用完成后,由于设计上的问题,即使手动释放 HttpClient,也极有可能无法释放套接字资源。

1.2 思考下面的代码,在远古时代,下面的代码将会造成 “套接字资源耗尽” 的异常

        public HttpClient CreateHttpClient()
        {
            return new HttpClient();
        }

        // 或者
        public async Task<string> GetData()
        {
            using (var client = new HttpClient())
            {
                var data = await client.GetAsync("https://www.cnblogs.com");
            }

            return null;
        }

1.3 继而引出了下面的使用方法,利用静态对象进行网络请求

        private static HttpClient httpClient = null;
        public HttpClient CreateHttpClient()
        {
            if (httpClient == null)
                httpClient = new HttpClient();

            return httpClient;
        }

1.4 上面使用静态对象的方式可以避免 “套接字资源耗尽” 的异常,但是,有一个致命的问题,当主机 DNS 更新时,你可能会收到另外一个异常

An error occurred while sending the request. Couldn't resolve host name An error occurred while sending the request. Couldn't resolve host name

1.5 该异常指示无法解析主机名称,其实就是因为静态 HttpClient 对象不会随着主机 DNS 更新而更新,这个时候,你通常需要做的就是:重启服务!

2. 正确的使用 HttpClient

2.1 时间来到了 .Net Core 的 2.2 时代(其实2.1就可以),官方推荐我们应该使用依赖注入的方式去使用 HttpClient,比如在 Startup.cs 的 ConfigureServices 方法中加入下面的代码

        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddHttpClient();
        }

2.2 然后再控制器中通过构造方法注入 HttpClient 对象进行使用

    public class ValuesController : ControllerBase
    {
        private HttpClient httpClient;
        public ValuesController(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        ...
    }

2.3 在新版本的 Asp.Net Core 中,Asp.Net Core 开发团队引入了 HttpClientFactory

        public HttpClient CreateHttpClient()
        {
            return HttpClientFactory.Create();
        }

2.4 HttpClientFactory 的主要工作就是创建 HttpClient 对象,但是在创建过程中,通过为每个 HttpClient 对象创建一个单独的清理句柄来对 HttpClient 进行跟踪和管理,以确保在对象使用完成后能够及时的释放网络请求的资源,也就是套接字,具体 HttpClientFactory 内部原理可参考 李志章-DotNetCore深入了解之三HttpClientFactory类.

2.5 更重要的是,HttpClientFactory 内部管理着一个连接句柄池,一旦高并发的到来,HttpClientFactory 内句柄池内使用完成但是未被释放的句柄将被重新使用,虽然使用 HttpClientFactory.Create() 每次都是返回一个新的 HttpClient 对象,但是其背后的管理句柄是可以复用的,换句话说就是 “套接字复用”,而且还不会有 DNS 无法同步更新的问题

2.6 所以现在我们明白了为什么要使用 HttpClientFactory 来创建 HttpClient 对象

3. 使用类型化的 HttpClient 客户端

3.1 在常规应用和微服务的应用场景中,都可以使用类型化的客户端,类型化客户端这个词如果不太好理解,那么你可以理解为为每个业务单独的使用一个 HttpClient 客户端,比如获取天气预报,思考下面的代码

public class WeatherService
{
    private HttpClient httpClient;
    public WeatherService(HttpClient httpClient)
    {
        this.httpClient = httpClient;
        this.httpClient.BaseAddress = new Uri("http://www.weather.com.cn");
        this.httpClient.Timeout = TimeSpan.FromSeconds(30);
    }

    public async Task<string> GetData()
    {
        var data = await this.httpClient.GetAsync("/data/sk/101010100.html");
        var result = await data.Content.ReadAsStringAsync();

        return result;
    }
}

3.2 为了在控制器中更好的使用 WeatherService,我们需要把 WeatherService 注入到服务中

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddHttpClient();
    }

    // 然后,在控制器中使用如下代码

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private WeatherService weatherService;
        public ValuesController(WeatherService weatherService)
        {
            this.weatherService = weatherService;
        }

        [HttpGet]
        public async Task<ActionResult> Get()
        {
            string result = string.Empty;
            try
            {
                result = await weatherService.GetData();
            }
            catch { }

            return new JsonResult(new { result });
        }
    }

3.3 运行程序,你将得到 北京市 的天气

{
result: "{"weatherinfo":{"city":"北京","cityid":"101010100","temp":"27.9","WD":"南风","WS":"小于3级","SD":"28%","AP":"1002hPa","njd":"暂无实况","WSE":"<3","time":"17:55","sm":"2.1","isRadar":"1","Radar":"JC_RADAR_AZ9010_JB"}}"
}

3.4 在微服务中,这种做法很常见,而且非常有用,通过使用类型化的客户端,除了在构造方法中注入 HttpClient 外,我们还可以注入任何需要的东西到 WeatherService 中,更重要的是,可以对业务应用扩展策略,还方便了管理
3.5 在 WeatherService 类型化客户端中,虽然每次都是创建了一个新的 HttpClient 对象,但是其内部的句柄和其它 HttpClient 是共用同一个句柄池,无需担心

4.对 HttpClient 应用策略

4.1 下面说到的策略组件是业内大名鼎鼎的 Polly (波莉),GitHub 地址:https://github.com/App-vNext/Polly
4.2 使用重试策略,参考 Polly 的 Wiki 示例代码,使用起来非常简单

首先需要从 NuGet 中引用包
Polly
Polly.Extensions.Http

4.3 接着在 Startup.cs ConfigureServices 方法中应用策略

        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddHttpClient<WeatherService>()
                    .SetHandlerLifetime(TimeSpan.FromMinutes(5))
                    .AddPolicyHandler(policy =>
                    {
                        return HttpPolicyExtensions.HandleTransientHttpError()
                                                   .WaitAndRetryAsync(3,
                                                   retryAttempt => TimeSpan.FromSeconds(2),
                                                   (exception, timeSpan, retryCount, context) =>
                                                   {
                                                       Console.ForegroundColor = ConsoleColor.Yellow;
                                                       Console.WriteLine("请求出错了:{0} | {1} ", timeSpan, retryCount);
                                                       Console.ForegroundColor = ConsoleColor.Gray;
                                                   });
                    });
        }

4.4 以上代码表示在请求发生错误的情况下,重试 3 次,每次 2 秒,针对高并发的请求,重试请求间隔建议使用随机值

结语

  • 本章着重介绍了 HttpClient 在 Asp.Net Core 中的前世今生,简单介绍了使用原理,介绍了各种使用 HttpClient 的方式
  • 介绍了使用了 Polly 对在类型化的客户端上使用 HttpClient 重试策略,因为对 Polly 理解不够,其它的策略就不再介绍,大家可以到 Polly 的 Wiki 上深入了解
  • 最后通过一个简单的获取天气预报的小实例来演示类型化的客户端使用场景

示例代码下载

https://files.cnblogs.com/files/viter/Ron.HttpClientDemo.zip

原文地址:https://www.cnblogs.com/viter/p/10086997.html

时间: 2024-08-11 09:56:44

Asp.Net Core 轻松学-HttpClient的演进和避坑的相关文章

Asp.Net Core 轻松学-多线程之Task(补充)

前言 ????在上一章 Asp.Net Core 轻松学-多线程之Task快速上手 文章中,介绍了使用Task的各种常用场景,但是感觉有部分内容还没有完善,在这里补充一下. 1. 任务的等待 在使用 Task 进行基于队列的异步任务(TAP)的时候,对于刚入门的同学来说,只是简单的了解了使用 Task 可以在后台处理异步任务,但是对于阻塞调用可能还有有一些不太明白,异步任务默认是不阻塞的执行过程,当一个 Task 被创建出来的时候,并没有被压入队列中,而是开始执行的时候,才会进入队列中:执行一个

Asp.Net Core 轻松学-使用MariaDB/MySql/PostgreSQL和支持多个上下文对象

前言 在上一篇文章中(Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库)[https://www.cnblogs.com/viter/p/10243577.html],介绍了 EFCore 连接 MSSQL 的使用方法,在本章中,将继续介绍如何利用 EFCore 连接到 MariaDB/MySql 和 PostgreSQL 数据库,同时,在一个项目中,如何添加多个数据库上下文对象,并在业务中使用多个上下文对象,通过这两章的学习,你将掌握使用 EFCore 连接 MS

Asp.Net Core 轻松学-多线程之取消令牌

前言 ????取消令牌(CancellationToken) 是 .Net Core 中的一项重要功能,正确并合理的使用 CancellationToken 可以让业务达到简化代码.提升服务性能的效果:当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,CancellationToken 将发挥非常重要的作用. 1. 多线程请求合并数据源 在一个很常见的业务场景中,比如当请求一个文章详细信息的时候,需要同时加载部分点赞用户和评论内容,这里一共有 3 个任务,如果按照常规的先请求文章信息,

Asp.Net Core 轻松学-从安装环境开始

Asp.Net Core 介绍 ????Asp.Net Core是微软新一代的跨平台开发框架,基友 C# 语言进行开发,该框架的推出,意味着微软从系统层面正式进击 Linux 服务器平台:从更新速度开来看,微软在 Asp.Net Core 的开发上可谓不遗余力. ????从开发者社区看,Asp.Net Core 有多火热,那么在过去 10 年间,C# 的开发者就有多压抑,过去 10 年以来,以 C# 开发语言为主业的开发者,几乎只能游历于所谓的企业级开发,其实就是做 OA.ERP.CRM 等传统

Asp.Net Core 轻松学-被低估的过滤器

前言 ????过滤器,从我们开始开发 Asp.Net 应用程序开始,就一直伴随在我们左右:Asp.Net Core 提供多种类型的过滤器,以满足多种多样的业务应用场景:并且在 Asp.Net Core 本身,过滤器的应用也非常广泛:但是,在实际的业务场景中,大部分开发人员只使用到其中 1 到 2 种类型,当然,这其中大部分可能性是由于业务场景的适用性使然,本文尝试简单介绍 Asp.Net Core 中提供的各种过滤器,以及实际的应用场景,希望对您有所帮助. 1. 介绍 1.1 作用范围 过滤器的

Asp.Net Core 轻松学-玩转配置文件

目录 前言 另类方式使用 hosting.json 使程序运行于多个端口 结语 前言 ????在 .NET Core 项目中,配置文件有着举足轻重的地位:与.NetFramework 不同的是,.NET Core 的配置文件都以 .json 结尾,这表示一个标准的 json 格式的文件:一个标准的 Asp.Net Core MVC 项目,一定带着一个 appsettings.json 文件,该文件便是项目默认配置文件,这和基于 .NetFramework 创建的 Asp.Net Web Appl

Asp.Net Core 轻松学-在.Net Core 中使用钩子

前言 ????Host startup hook,是2.2中提供的一项新的功能,通过使用主机启动钩子,允许开发人员在不修改代码的情况下,在服务启动之前注入代码:通过使用钩子,可以对已部署好的服务在服务启动期间自定义托管程序的行为:通过使用钩子,可以对服务进行跟踪或者遥测,也可以在服务启动前对托管环境进行健康检查:还可以通过钩子动态加载程序集进行依赖注入等功能. 什么是钩子 钩子的作用原理是通过设置环境变量 DOTNET_STARTUP_HOOKS 的值将钩子程序挂载到托管程序之中,在托管程序启动

Asp.Net Core 轻松学-经常使用异步的你,可能需要看看这个文章

前言 事情的起因是由于一段简单的数据库连接代码引起,这段代码从语法上看,是没有任何问题:但是就是莫名其妙的报错了,这段代码极其简单,就是打开数据库连接,读取一条记录,然后立即更新到数据库中.但是,惨痛的事实证明,老司机也是会翻车的. 1. 异常的发生来得太突然 1.1 引起不舒适的代码片段 [HttpPut] public async void Put([FromBody] TopicViewModel model) { var topic = this.context.Topics.Where

asp.net core中使用HttpClient实现Post和Get的同步异步方法

 准备工作 1.visual studio 2015 update3开发环境 2.net core 1.0.1 及以上版本  目录 1.HttpGet方法 2.HttpPost方法 3.使用示例 4.代码下载  1 HttpGet /// <summary> /// 使用Get方法获取字符串结果(没有加入Cookie) /// </summary> /// <param name="url"></param> /// <return