zoukankan      html  css  js  c++  java
  • 第十七节:.Net Core中新增HttpClientFactory的前世今生

    一. 背景

    1.前世

      提到HttpClient,在传统的.Net版本中简直臭名昭著,因为我们安装官方用法 using(var httpClient = new HttpClient()),当然可以Dispose,但是在高并发的情况下,连接来不及释放,socket被耗尽,然后就会出现一个喜闻乐见的错误:即各种套接字的问题。

     Unable to connect to the remote serverSystem.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.

    PS:当然我们可以通过修改注册表的默认值,来人为的减少超时时间,但可能会引起其他莫名其妙的问题:

     [HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParametersTcpTimedWaitDelay])

    2.我们之前的解决方案

      把HttpClient做成全局单例的,通过共享一个实例,减少了套接字的浪费,实际上由于套接字重用而传输快一点。

     关于单例的封装,详见这篇: https://www.cnblogs.com/yaopengfei/p/10301779.html

      封装成单例的也会有些不灵活的地方:

    (1).因为是复用的 HttpClient,那么一些公共的设置就没办法灵活的调整了,如请求头的自定义。

    (2).因为 HttpClient 请求每个 url 时,会缓存该url对应的主机 ip,从而会导致 DNS 更新失效(TTL 失效了)。

    3.HttpClientFactiory应运而生

      HttpClientFactory 是 ASP.NET CORE 2.1 中新增加的功能。顾名思义 HttpClientFactory 就是 HttpClient 的工厂,内部已经帮我们处理好了对 HttpClient 的管理,不需要我们人工进行对象释放,同时,支持自定义请求头,支持 DNS 更新。

    分析:

      HttpClient 继承自 HttpMessageInvoker,而 HttpMessageInvoker 实质就是HttpClientHandler。HttpClientFactory 创建的 HttpClient,也即是 HttpClientHandler,只是这些个 HttpClient 被放到了“池子”中,工厂每次在 create 的时候会自动判断是新建还是复用。(默认生命周期为 2min)。简单的理解成 Task 和 Thread 的关系。

    4. 补充请求方式的说明

      其中Post请求有两种,分别是: "application/x-www-form-urlencoded"表单提交的方式 和 "application/json" Json格式提交的方式。

    (1). Post的表单提交的格式为:"userName=admin&pwd=123456"。

    (2). Post的Json的提交格式为:将实体(类)转换成json字符串。

    二. 几种用法

    用到的服务器端的方法:

        /// <summary>
        /// 充当服务端接口
        /// </summary>
        public class ServerController : Controller
        {
            [HttpGet]
            public string CheckLogin(string userName, string pwd)
            {
                if (userName == "admin" && pwd == "123456")
                {
                    return "ok";
                }
                else
                {
                    return "error";
                }
            }
    
            [HttpPost]
            public string Register1(string userName, string pwd)
            {
                if (userName == "admin" && pwd == "123456")
                {
                    return "ok";
                }
                else
                {
                    return "error";
                }
            }
    
            [HttpPost]
            public string Register2([FromBody]UserInfor model)
            {
                if (model.userName == "admin" && model.pwd == "123456")
                {
                    return "ok";
                }
                else
                {
                    return "error";
                }
            }
    
        }
    View Code

    1. 基本用法

    A.步骤

      (1).在ConfigService方法中注册服务:services.AddHttpClient();

      (2).通过构造函数全局注入IHttpClientFactory对象,或者通过[FromServices]给某个方法注入。

      (3).利用SendAsync方法和HttpRequestMessage对象(可以配置请求方式、表头、请求内容)进行各种请求的异步和同步发送

      (PS:下面无论哪种用法,这里都通过SendAsync和HttpRequestMessage进行演示)

    B.适用场景

       以这种方式使用 IHttpClientFactory 适合重构现有应用。 这不会影响 HttpClient 的使用方式。 在当前创建HttpClient 实例的位置, 使用对 CreateClient 的调用替换这些匹配项。

    特别补充:利用GetAsync和PostAsync方法来发送请求的方式,详见下面代码。

    2. 命名客户端

    A.步骤

      (1).在ConfigService方法中注册服务:services.AddHttpClient("client1",c=>{ 相关默认配置 });

      (2).通过构造函数全局注入IHttpClientFactory对象,或者通过[FromServices]给某个方法注入

      (3).创建Client对象的时候指定命名 CreateClient("client1");其它用法都相同了。

    B.适用场景

      应用需要有许多不同的 HttpClient 用法(每种用法的配置都不同),可以视情况使用命名客户端。可以在 HttpClient 中注册时指定命名 Startup.ConfigureServices 的配置请求地址的公共部分,表头等。

    3. 类型化客户端

    A.本质

      新建一个类,在类里注入HttpClient对象,在构造函数中进行配置,或者在Startup中进行配置,然后把请求相关的业务封装到方法中,外层直接调用方法即可。

    B.步骤

      (1).新建UserService类,通过构造函数注入HttpClient对象,然后将请求相关的配置封装在一个方法Login中。

      (2).在ConfigService方法中注册服务:services.AddHttpClient<UserService>();

      (3).通过构造函数全局注入UserService对象,或者通过[FromServices]给某个方法注入。

      (4).调用对应的方法即可。

    C.适用场景

      根据个人喜好选择即可

    分享上述全部代码:

     1    public void ConfigureServices(IServiceCollection services)
     2         {
     3             services.Configure<CookiePolicyOptions>(options =>
     4             {
     5                 // This lambda determines whether user consent for non-essential cookies is needed for a given request.
     6                 options.CheckConsentNeeded = context => true;
     7                 options.MinimumSameSitePolicy = SameSiteMode.None;
     8             });
     9 
    10             //下面是注册HttpClient服务
    11             //1. 基本用法
    12             services.AddHttpClient();
    13             //2. 命名客户端
    14             services.AddHttpClient("client1", c =>
    15             {
    16                 c.BaseAddress = new Uri("http://localhost:15319/"); 
    17                 c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    18                 c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    19             });
    20             //3. 类型化客户端
    21             services.AddHttpClient<UserService>();
    22             //可以根据喜好在注册服务的时候配置,就不需要在UserService构造函数中配置了
    23             //services.AddHttpClient<UserService>(c =>
    24             //{
    25             //    c.BaseAddress = new Uri("http://localhost:15319/");
    26             //    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    27             //    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    28             //});
    29             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    30         }
    ConfigureServices
      1  public class HomeController : Controller
      2     {
      3         private readonly IHttpClientFactory _clientFactory;
      4         public HomeController(IHttpClientFactory clientFactory)
      5         {
      6             _clientFactory = clientFactory;
      7         }
      8 
      9         public async Task<IActionResult> Index([FromServices] UserService userService)
     10         {
     11             string url1 = "http://localhost:15319/Server/CheckLogin?userName=admin&pwd=123456";
     12             string url2 = "http://localhost:15319/Server/Register1";
     13             string url3 = "http://localhost:15319/Server/Register2";
     14 
     15 
     16             string url4 = "Server/CheckLogin?userName=admin&pwd=123456";
     17 
     18             /*********************************************一.基本用法*********************************************/
     19 
     20             #region 01-基本用法(Get请求)
     21             {
     22                 var request = new HttpRequestMessage(HttpMethod.Get, url1);
     23                 request.Headers.Add("Accept", "application/vnd.github.v3+json");
     24                 request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
     25                 var client = _clientFactory.CreateClient();
     26                 var response = await client.SendAsync(request);
     27                 string result = "";
     28                 if (response.IsSuccessStatusCode)
     29                 {
     30                     result = await response.Content.ReadAsStringAsync();
     31                 }
     32                 ViewBag.result = result;
     33 
     34             }
     35             #endregion
     36 
     37             #region 02-基本用法(Post请求-表单提交)
     38             {
     39                 var request = new HttpRequestMessage(HttpMethod.Post, url2);
     40                 //表头的处理
     41                 request.Headers.Add("Accept", "application/vnd.github.v3+json");
     42                 request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
     43                 //内容的处理
     44                 request.Content = new StringContent("userName=admin&pwd=123456", Encoding.UTF8, "application/x-www-form-urlencoded");
     45                 var client = _clientFactory.CreateClient();
     46                 var response = await client.SendAsync(request);
     47                 string result = "";
     48                 if (response.IsSuccessStatusCode)
     49                 {
     50                     result = await response.Content.ReadAsStringAsync();
     51                 }
     52             }
     53             #endregion
     54 
     55             #region 03-基本用法(Post请求-JSON格式提交)
     56             {
     57                 var request = new HttpRequestMessage(HttpMethod.Post, url3);
     58                 //表头的处理
     59                 request.Headers.Add("Accept", "application/vnd.github.v3+json");
     60                 request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
     61                 //内容的处理
     62                 var user = new
     63                 {
     64                     userName = "admin",
     65                     pwd = "123456"
     66                 };
     67                 request.Content = new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json");
     68                 var client = _clientFactory.CreateClient();
     69                 var response = await client.SendAsync(request);
     70                 string result = "";
     71                 if (response.IsSuccessStatusCode)
     72                 {
     73                     result = await response.Content.ReadAsStringAsync();
     74                 }
     75             }
     76             #endregion
     77 
     78             #region 04-补充其他写法
     79             {
     80                 //上面的三个方法都是利用SendAsync方法配合HttpRequestMessage类来发送Get和两种post请求的,所有的参数设置都是基于HttpRequestMessage对象。
     81                 //在这里再次补充一下直接利用 GetAsync 和 PostAsync 方法直接来发送Get和post请求(直接.Result不异步了)
     82 
     83                 //1. Get请求
     84                 {
     85                     var client = _clientFactory.CreateClient();
     86                     //配置表头
     87                     client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
     88                     client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
     89                     var response = client.GetAsync(url1).Result;
     90                     string result = "";
     91                     if (response.IsSuccessStatusCode)
     92                     {
     93                         result = response.Content.ReadAsStringAsync().Result;
     94                     }
     95                 }
     96                 //2. Post请求-表单提交
     97                 {
     98                     var client = _clientFactory.CreateClient();
     99                     //配置表头
    100                     client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    101                     client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    102                     //配置请求内容
    103                     var content = new StringContent("userName=admin&pwd=123456", Encoding.UTF8, "application/x-www-form-urlencoded");
    104                     var response = client.PostAsync(url2, content).Result;
    105                     string result = "";
    106                     if (response.IsSuccessStatusCode)
    107                     {
    108                         result = response.Content.ReadAsStringAsync().Result;
    109                     }
    110 
    111                 }
    112 
    113                 //3. Post请求-JSON提交
    114                 {
    115                     var client = _clientFactory.CreateClient();
    116                     //配置表头
    117                     client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    118                     client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    119                     //配置请求内容
    120                     var user = new
    121                     {
    122                         userName = "admin",
    123                         pwd = "123456"
    124                     };
    125                     var content = new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json");
    126                     var response = client.PostAsync(url3, content).Result;
    127                     string result = "";
    128                     if (response.IsSuccessStatusCode)
    129                     {
    130                         result = response.Content.ReadAsStringAsync().Result;
    131                     }
    132                 }
    133 
    134             }
    135             #endregion
    136 
    137 
    138             /*********************************************二.命名客户端*********************************************/
    139 
    140             #region 命名客户端(Get请求)
    141             {
    142                 var request = new HttpRequestMessage(HttpMethod.Get, url4);
    143                 //配置调用的名称
    144                 var client = _clientFactory.CreateClient("client1");
    145                 var response = await client.SendAsync(request);
    146                 string result = "";
    147                 if (response.IsSuccessStatusCode)
    148                 {
    149                     result = await response.Content.ReadAsStringAsync();
    150                 }
    151             }
    152             #endregion
    153 
    154             /*********************************************三.类型化客户端*********************************************/
    155 
    156             #region 类型化客户端(Get请求)
    157             {
    158                 string result = await userService.Login(url4);
    159             }
    160             #endregion
    161 
    162 
    163             return View();
    164         }
    165     }
    View Code
     1  /// <summary>
     2     /// 类型化客户端
     3     /// </summary>
     4     public class UserService
     5     {
     6         public HttpClient _client;
     7         public UserService(HttpClient client)
     8         {
     9             client.BaseAddress = new Uri("http://localhost:15319/");
    10             client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    11             client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    12             _client = client;
    13         }
    14 
    15         public async Task<string> Login(string urlContent)
    16         {
    17             var response = await _client.GetAsync(urlContent);
    18             response.EnsureSuccessStatusCode();
    19             var result = await response.Content.ReadAsStringAsync();
    20             return result;
    21         }
    22     }
    类型化客户端-UserService

    4. 总结

      它们之间不存在严格的优先级。 最佳方法取决于应用的约束条件。

    三. 与Refit结合

     不做深入研究,可参考:

       https://www.xcode.me/code/refit-the-automatic-type-safe-rest-library
       https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.1#outgoing-request-middleware

    四. 结合框架进行封装

      封装思路:将内容和请求方式等一系列操作封装到方法里,如果需要配置表头,建议采用命名客户端的方式在ConfigureService中进行配置, 需要事先注册好服务,然后把实例化好的IHttpClientFactory对象传入到方法里。

      封装了三个方法,分别来处理:Get、两种Post请求

      需要用到:【Microsoft.Extensions.Http】程序集,Core MVC中已经默认引入了。

     代码分享:

     1     /// <summary>
     2     /// 基于HttpClientFactory的请求封装
     3     /// </summary>
     4     public class RequestHelp
     5     {
     6         /// <summary>
     7         /// Get请求
     8         /// </summary>
     9         /// <param name="clientFactory">实例化好的HttpClientFactory对象</param>
    10         /// <param name="url">请求地址</param>
    11         /// <param name="clientName">注册名称,默认不指定</param>
    12         /// <returns></returns>
    13         public static string MyGetRequest(IHttpClientFactory clientFactory, string url, string clientName = "")
    14         {
    15             var request = new HttpRequestMessage(HttpMethod.Get, url);
    16             var client = clientName == "" ? clientFactory.CreateClient() : clientFactory.CreateClient(clientName);
    17             var response = client.SendAsync(request).Result;
    18             var myResult = response.Content.ReadAsStringAsync().Result;
    19             return myResult;
    20         }
    21 
    22         /// <summary>
    23         /// Post请求-表单形式
    24         /// </summary>
    25         /// <param name="clientFactory">实例化好的HttpClientFactory对象</param>
    26         /// <param name="url">请求地址</param>
    27         /// <param name="content">请求内容</param>
    28         /// <param name="clientName">注册名称,默认不指定</param>
    29         /// <returns></returns>
    30         public static string MyPostRequest(IHttpClientFactory clientFactory, string url,string content, string clientName = "")
    31         {
    32             var request = new HttpRequestMessage(HttpMethod.Post, url);
    33             //内容的处理
    34              request.Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
    35             var client = clientName == "" ? clientFactory.CreateClient() : clientFactory.CreateClient(clientName);
    36             var response = client.SendAsync(request).Result;
    37             var myResult = response.Content.ReadAsStringAsync().Result;
    38             return myResult;
    39         }
    40 
    41         /// <summary>
    42         /// Post请求-Json形式
    43         /// </summary>
    44         /// <param name="clientFactory">实例化好的HttpClientFactory对象</param>
    45         /// <param name="url">请求地址</param>
    46         /// <param name="content">请求内容</param>
    47         /// <param name="clientName">注册名称,默认不指定</param>
    48         /// <returns></returns>
    49         public static string MyPostRequestJson(IHttpClientFactory clientFactory, string url, object content, string clientName = "")
    50         {
    51             var request = new HttpRequestMessage(HttpMethod.Post, url);
    52             //内容的处理
    53             request.Content = new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json");
    54             var client = clientName == "" ? clientFactory.CreateClient() : clientFactory.CreateClient(clientName);
    55             var response = client.SendAsync(request).Result;
    56             var myResult = response.Content.ReadAsStringAsync().Result;
    57             return myResult;
    58         }
    59 
    60     }
     1   public class HomeController : Controller
     2     {
     3         private readonly IHttpClientFactory _clientFactory;
     4         public HomeController(IHttpClientFactory clientFactory)
     5         {
     6             _clientFactory = clientFactory;
     7         }
     8 
     9         public async Task<IActionResult> Index([FromServices] UserService userService)
    10         {
    11             string url1 = "http://localhost:15319/Server/CheckLogin?userName=admin&pwd=123456";
    12             string url2 = "http://localhost:15319/Server/Register1";
    13             string url3 = "http://localhost:15319/Server/Register2";
    14             string url4 = "Server/CheckLogin?userName=admin&pwd=123456";
    15 
    16 
    17             /*********************************************测试框架封装*********************************************/
    18 
    19             #region 测试框架封装
    20             {
    21                 var result1 = RequestHelp.MyGetRequest(_clientFactory, url1);
    22                 var result2 = RequestHelp.MyGetRequest(_clientFactory, url4, "client1");
    23                 //Post-表单提交
    24                 var result3 = RequestHelp.MyPostRequest(_clientFactory, url2, "userName=admin&pwd=123456");
    25                 //Post-Json提交
    26                 var user = new
    27                 {
    28                     userName = "admin",
    29                     pwd = "123456"
    30                 };
    31                 var result4 = RequestHelp.MyPostRequestJson(_clientFactory, url3, user);
    32 
    33             } 
    34             #endregion
    35 
    36             return View();
    37         }
    38     }

    五. 如何在客户端调用

       前面的介绍都是基于Core MVC来发送请求,然后可以注入 IHttpClientFactory 对象,那么如何在控制台上调用呢? 这是一类通用的问题,看下面代码,获取 IHttpClientFactory 对象,然后后续的Get和Post请求的写法和前面介绍的完全相同,可以根据自己的喜好进行选择。

    var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
    IHttpClientFactory httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();

    分享一个完整的客户端请求写法:

                 var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
                IHttpClientFactory httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
                string url2 = "http://XXX:8055/Home/SendAllMsg";
    
                var client = httpClientFactory.CreateClient();
                var content = new StringContent("msg=123", Encoding.UTF8, "application/x-www-form-urlencoded");
                var response = client.PostAsync(url2, content).Result;
                string result = "";
                if (response.IsSuccessStatusCode)
                {
                    result = response.Content.ReadAsStringAsync().Result;
                }

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    Global.asax 事件备忘
    JavaScript异常捕捉
    还记得 virtual 吗?我们来温故知新下吧。
    开发(ASP.NET程序)把写代码写至最有面向对象味道
    MVC中实现 "加载更多..."
    js和C#中的编码和解码(备忘)
    System.AccessViolationException: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏
    10种提高WordPress访问速度的方法
    使用Python3实现Telnet功能
    读书计划(不断更新)201904
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/11300888.html
Copyright © 2011-2022 走看看