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 type = typeof(T);
188 if (type.IsPrimitive || type == typeof(string))
189 {
190 return (T)Convert.ChangeType(responseContent, typeof(T));
191 }
192 else
193 {
194 return JsonUtil.Deserialize<T>(responseContent);
195 }
196 }
197 else
198 {
199 throw new CustomerHttpException(response.StatusCode.ToString(), responseContent);
200 }
201 }
202 catch (Exception ex)
203 {
204 _logger.LogError($"{method.ToString()}:{url},body:{requestBody}, Error:{ex.ToString()}");
205 throw new Exception($"{method.ToString()}:{url} Error", ex);
206 }
207
208 }
209 /// <summary>
210 /// Build HttpClient
211 /// </summary>
212 /// <param name="timeoutSecond"></param>
213 /// <returns></returns>
214 private HttpClient BuildHttpClient(Dictionary<string, string> dicDefaultHeaders, int? timeoutSecond)
215 {
216 var httpClient = _httpClientFactory.CreateClient();
217 httpClient.DefaultRequestHeaders.Clear(); //in order that the client is not affected by the last request,it need to clear DefaultRequestHeaders
218 httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
219 if (dicDefaultHeaders != null)
220 {
221 foreach (var headerItem in dicDefaultHeaders)
222 {
223 if (!httpClient.DefaultRequestHeaders.Contains(headerItem.Key))
224 {
225 httpClient.DefaultRequestHeaders.Add(headerItem.Key, headerItem.Value);
226 }
227 }
228 }
229 if (timeoutSecond != (int?)null)
230 {
231 httpClient.Timeout = TimeSpan.FromSeconds(timeoutSecond.Value);
232 }
233 return httpClient;
234 }
235
236 /// <summary>
237 /// Generate HttpRequestMessage
238 /// </summary>
239 /// <param name="url"></param>
240 /// <param name="requestBody"></param>
241 /// <param name="method"></param>
242 /// <param name="dicHeaders"></param>
243 /// <returns></returns>
244 private HttpRequestMessage GenerateHttpRequestMessage(string url, string requestBody, HttpMethod method, Dictionary<string, string> dicHeaders)
245 {
246 var request = new HttpRequestMessage(method, url);
247 if (!string.IsNullOrEmpty(requestBody))
248 {
249 request.Content = new StringContent(requestBody);
250 }
251 if (dicHeaders != null)
252 {
253 foreach (var header in dicHeaders)
254 {
255 request.Headers.Add(header.Key, header.Value);
256 }
257 }
258 return request;
259 }
260 /// <summary>
261 /// Generate StringContent
262 /// </summary>
263 /// <param name="requestBody"></param>
264 /// <param name="dicHeaders"></param>
265 /// <returns></returns>
266 private StringContent GenerateStringContent(string requestBody, Dictionary<string, string> dicHeaders)
267 {
268 var content = new StringContent(requestBody);
269 if (dicHeaders != null)
270 {
271 foreach (var headerItem in dicHeaders)
272 {
273 content.Headers.Add(headerItem.Key, headerItem.Value);
274 }
275 }
276 return content;
277 }
278 }

.NetCore简单封装基于IHttpClientFactory的HttpClient请求_json

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,从而解析出具体的错误信息。