IHttpClientFactory是什么?為什么出現了IHttpClientFactory
一、IHttpClientFactory是什么?
IHttpClientFactory是.netcore2.1才開始引入的,是HttpClient的工廠接口,它為我們提供了獲取HttpClient的接口,它幫助我們維護HttpClient的生命周期。當我們需要HttpClient訪問網絡時,它會自動幫我們獲取或者是創建HttpClient(存在空閑的HttpClient時,直接提供;當不存在可用的HttpClient時自動創建)。它相當於HttpClient池。
二、為什么出現IHttpClientFactory?
傳統的HttpClient創建后,其占用了Socket資源並且其不會是及時的回收。我們每次new一個HttpClient時是一個全新的對象,所以在高並發下又是會導致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.)。而如果采用單例或者靜態的HttpClient,卻達不到高效的使用網絡請求,而且當訪問不同的url時,單例或者靜態的HttpClient往往會導致訪問混亂而出現錯誤。
.NetCore簡單封裝基於IHttpClientFactory的HttpClient請求
1 public class HttpWebClient 2 { 3 4 private IHttpClientFactory _httpClientFactory; 5 private readonly ILogger<HttpWebClient> _logger; 6 public HttpWebClient(IHttpClientFactory httpClientFactory, ILogger<HttpWebClient> logger) 7 { 8 this._httpClientFactory = httpClientFactory; 9 this._logger = logger; 10 } 11 12 /// <summary> 13 /// Get 14 /// </summary> 15 /// <param name="url"></param> 16 /// <param name="dicHeaders"></param> 17 /// <param name="timeoutSecond"></param> 18 /// <returns></returns> 19 public async Task<T> GetAsync<T>(string url, Dictionary<string, string> dicHeaders, int timeoutSecond = 180) 20 { 21 try 22 { 23 var client = BuildHttpClient(dicHeaders, timeoutSecond); 24 var response = await client.GetAsync(url); 25 var responseContent = await response.Content.ReadAsStringAsync(); 26 if (response.IsSuccessStatusCode) 27 { 28 return JsonUtil.Deserialize<T>(responseContent); 29 } 30 else 31 { 32 throw new CustomerHttpException(response.StatusCode.ToString(), responseContent); 33 } 34 } 35 catch (Exception ex) 36 { 37 _logger.LogError($"HttpGet:{url} Error:{ex.ToString()}"); 38 throw new Exception($"HttpGet:{url} Error", ex); 39 } 40 } 41 /// <summary> 42 /// Post 43 /// </summary> 44 /// <param name="url"></param> 45 /// <param name="requestBody"></param> 46 /// <param name="dicHeaders"></param> 47 /// <param name="timeoutSecond"></param> 48 /// <returns></returns> 49 public async Task<T> PostAsync<T>(string url, string requestBody, Dictionary<string, string> dicHeaders, int timeoutSecond = 180) 50 { 51 try 52 { 53 var client = BuildHttpClient(null, timeoutSecond); 54 var requestContent = GenerateStringContent(requestBody, dicHeaders); 55 var response = await client.PostAsync(url, requestContent); 56 var responseContent = await response.Content.ReadAsStringAsync(); 57 if (response.IsSuccessStatusCode) 58 { 59 var result = JsonUtil.Deserialize<T>(responseContent); 60 return result; 61 } 62 else 63 { 64 throw new CustomerHttpException(response.StatusCode.ToString(), responseContent); 65 } 66 } 67 catch (Exception ex) 68 { 69 _logger.LogError($"HttpPost:{url},body:{requestBody} Error:{ex.ToString()}"); 70 throw new Exception($"HttpPost:{url} Error", ex); 71 } 72 } 73 /// <summary> 74 /// Put 75 /// </summary> 76 /// <param name="url"></param> 77 /// <param name="requestBody"></param> 78 /// <param name="dicHeaders"></param> 79 /// <param name="timeoutSecond"></param> 80 /// <returns></returns> 81 public async Task<T> PutAsync<T>(string url, string requestBody, Dictionary<string, string> dicHeaders, int timeoutSecond = 180) 82 { 83 try 84 { 85 var client = BuildHttpClient(null, timeoutSecond); 86 var requestContent = GenerateStringContent(requestBody, dicHeaders); 87 var response = await client.PutAsync(url, requestContent); 88 var responseContent = await response.Content.ReadAsStringAsync(); 89 if (response.IsSuccessStatusCode) 90 { 91 var result = JsonUtil.Deserialize<T>(responseContent); 92 return result; 93 } 94 else 95 { 96 throw new CustomerHttpException(response.StatusCode.ToString(), responseContent); 97 } 98 } 99 catch (Exception ex) 100 { 101 _logger.LogError($"HttpPut:{url},Body:{requestBody}, Error:{ex.ToString()}"); 102 throw new Exception($"HttpPut:{url} Error", ex); 103 } 104 } 105 106 /// <summary> 107 /// Patch 108 /// </summary> 109 /// <param name="url"></param> 110 /// <param name="requestString"></param> 111 /// <param name="dicHeaders"></param> 112 /// <param name="timeoutSecond"></param> 113 /// <returns></returns> 114 public async Task<T> PatchAsync<T>(string url, string requestBody, Dictionary<string, string> dicHeaders, int timeoutSecond = 180) 115 { 116 try 117 { 118 var client = BuildHttpClient(null, timeoutSecond); 119 var requestContent = GenerateStringContent(requestBody, dicHeaders); 120 var response = await client.PatchAsync(url, requestContent); 121 var responseContent = await response.Content.ReadAsStringAsync(); 122 if (response.IsSuccessStatusCode) 123 { 124 var result = JsonUtil.Deserialize<T>(responseContent); 125 return result; 126 } 127 else 128 { 129 throw new CustomerHttpException(response.StatusCode.ToString(), responseContent); 130 } 131 } 132 catch (Exception ex) 133 { 134 _logger.LogError($"HttpPatch:{url},body:{requestBody}, Error:{ex.ToString()}"); 135 throw new Exception($"HttpPatch:{url} Error", ex); 136 } 137 } 138 /// <summary> 139 /// Delete 140 /// </summary> 141 /// <param name="url"></param> 142 /// <param name="dicHeaders"></param> 143 /// <param name="timeoutSecond"></param> 144 /// <returns></returns> 145 public async Task<T> DeleteAsync<T>(string url, Dictionary<string, string> dicHeaders, int timeoutSecond = 180) 146 { 147 try 148 { 149 var client = BuildHttpClient(dicHeaders, timeoutSecond); 150 var response = await client.DeleteAsync(url); 151 var responseContent = await response.Content.ReadAsStringAsync(); 152 if (response.IsSuccessStatusCode) 153 { 154 var result = JsonUtil.Deserialize<T>(responseContent); 155 return result; 156 } 157 else 158 { 159 throw new CustomerHttpException(response.StatusCode.ToString(), responseContent); 160 } 161 } 162 catch (Exception ex) 163 { 164 _logger.LogError($"HttpDelete:{url}, Error:{ex.ToString()}"); 165 throw new Exception($"HttpDelete:{url} Error", ex); 166 } 167 } 168 /// <summary> 169 /// common request 170 /// </summary> 171 /// <param name="url"></param> 172 /// <param name="method"></param> 173 /// <param name="requestBody"></param> 174 /// <param name="dicHeaders"></param> 175 /// <param name="timeoutSecond"></param> 176 /// <returns></returns> 177 public async Task<T> ExecuteAsync<T>(string url, HttpMethod method, string requestBody, Dictionary<string, string> dicHeaders, int timeoutSecond = 180) 178 { 179 try 180 { 181 var client = BuildHttpClient(null, timeoutSecond); 182 var request = GenerateHttpRequestMessage(url, requestBody, method, dicHeaders); 183 var response = await client.SendAsync(request); 184 var responseContent = await response.Content.ReadAsStringAsync(); 185 if (response.IsSuccessStatusCode) 186 { 187 var result = JsonUtil.Deserialize<T>(responseContent); 188 return result; 189 } 190 else 191 { 192 throw new CustomerHttpException(response.StatusCode.ToString(), responseContent); 193 } 194 } 195 catch (Exception ex) 196 { 197 _logger.LogError($"{method.ToString()}:{url},body:{requestBody}, Error:{ex.ToString()}"); 198 throw new Exception($"{method.ToString()}:{url} Error", ex); 199 } 200 201 } 202 /// <summary> 203 /// Build HttpClient 204 /// </summary> 205 /// <param name="timeoutSecond"></param> 206 /// <returns></returns> 207 private HttpClient BuildHttpClient(Dictionary<string, string> dicDefaultHeaders, int? timeoutSecond) 208 { 209 var httpClient = _httpClientFactory.CreateClient(); 210 httpClient.DefaultRequestHeaders.Clear(); //in order that the client is not affected by the last request,it need to clear DefaultRequestHeaders 211 httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 212 if (dicDefaultHeaders != null) 213 { 214 foreach (var headerItem in dicDefaultHeaders) 215 { 216 if (!httpClient.DefaultRequestHeaders.Contains(headerItem.Key)) 217 { 218 httpClient.DefaultRequestHeaders.Add(headerItem.Key, headerItem.Value); 219 } 220 } 221 } 222 if (timeoutSecond != (int?)null) 223 { 224 httpClient.Timeout = TimeSpan.FromSeconds(timeoutSecond.Value); 225 } 226 return httpClient; 227 } 228 229 /// <summary> 230 /// Generate HttpRequestMessage 231 /// </summary> 232 /// <param name="url"></param> 233 /// <param name="requestBody"></param> 234 /// <param name="method"></param> 235 /// <param name="dicHeaders"></param> 236 /// <returns></returns> 237 private HttpRequestMessage GenerateHttpRequestMessage(string url, string requestBody, HttpMethod method, Dictionary<string, string> dicHeaders) 238 { 239 var request = new HttpRequestMessage(method, url); 240 if (!string.IsNullOrEmpty(requestBody)) 241 { 242 request.Content = new StringContent(requestBody); 243 } 244 if (dicHeaders != null) 245 { 246 foreach (var header in dicHeaders) 247 { 248 request.Headers.Add(header.Key, header.Value); 249 } 250 } 251 return request; 252 } 253 /// <summary> 254 /// Generate StringContent 255 /// </summary> 256 /// <param name="requestBody"></param> 257 /// <param name="dicHeaders"></param> 258 /// <returns></returns> 259 private StringContent GenerateStringContent(string requestBody, Dictionary<string, string> dicHeaders) 260 { 261 var content = new StringContent(requestBody); 262 if (dicHeaders != null) 263 { 264 foreach (var headerItem in dicHeaders) 265 { 266 content.Headers.Add(headerItem.Key, headerItem.Value); 267 } 268 } 269 return content; 270 } 271 272 273 }
CustomerHttpException類的簡單定義
1 public class CustomerHttpException : Exception 2 { 3 public string ErrorCode { get; set; } 4 public string ErrorMessage { get; set; } 5 public CustomerHttpException() : base() 6 { } 7 public CustomerHttpException(string errorCode, string errorMessage) : base() 8 { 9 this.ErrorCode = errorCode; 10 this.ErrorMessage = ErrorMessage; 11 } 12 }
以上是簡單的Http請求封裝。其中一些值得注意的點
1、創建的HttpClient是由HttpClientFactory統一管理的,所以當指定了DefaultRequestHeaders時,下次再次從HttpClientFactory中獲取時可能帶着上次的Header信息。所以需要對其進行Clear。
2、封裝了統一的調用接口ExecuteAsync,建議采用該接口進行http請求,此時就不會去配置DefaultRequestHeaders,而是將Header信息配置在請求信息中。
3、自定義了CustomerHttpException,主要是為了支持ResultFul風格的api。通過外層捕獲CustomerHttpException,從而解析出具體的錯誤信息。