第十七節:.Net Core中新增HttpClientFactory的前世此生

一. 背景html

1.前世git

  提到HttpClient,在傳統的.Net版本中簡直臭名昭著,由於咱們安裝官方用法 using(var httpClient = new HttpClient()),固然能夠Dispose,可是在高併發的狀況下,鏈接來不及釋放,socket被耗盡,而後就會出現一個喜聞樂見的錯誤:即各類套接字的問題。github

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

PS:固然咱們能夠經過修改註冊表的默認值,來人爲的減小超時時間,但可能會引發其餘莫名其妙的問題:xcode

 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])緩存

2.咱們以前的解決方案服務器

  把HttpClient作成全局單例的,經過共享一個實例,減小了套接字的浪費,實際上因爲套接字重用而傳輸快一點。cookie

 關於單例的封裝,詳見這篇: http://www.javashuo.com/article/p-scecuthp-gb.html併發

  封裝成單例的也會有些不靈活的地方:app

(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     }

 

 

 

 

!

  • 做       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 本人才疏學淺,用郭德綱的話說「我是一個小學生」,若有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文連接或在文章開頭加上本人博客地址,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索