一. 背景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"; } } }
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 }
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 }
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 }
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 }
!