如果找的是core的HttpClientFactory 出门右转。
-
官方写法,高并发下,TCP连接不能快速释放,导致端口占完,无法连接
Dispose 不是马上关闭tcp连接
主动关闭的一方为什么不能马上close而是进入timewait状态:TCP四次挥手客户端关闭链接为什么要等待2倍MSL
正确写法一个域(一个地址) 保证一个静态httpclient操作,保证重用tcp连接。
-
如果HttpClient唯一,如果请求头内容需要变化怎么办,异常:"集合已修改;可能无法执行枚举操作"
HttpClient有个接口SendAsync。看源码知道其实HttpClient内部get,post,put,delete最终都是调用SendAsync。
这个方法可以允许用户传递HttpRequestMessage,内部包含(HttpRequestHeaders)
-
HttpClient 死锁
关于Task同步上下文 造成死锁问题就不多解释。避免方法就是ConfigureAwait(false)或者await always。最好是await always。传送门
说下不用await 而使用类似HttpClient.GetStringAsync(uri).Result 直接同步获取为什么没有死锁
因为HttpClient源码里面用到async await的地方几乎都加了ConfigureAwait(false)。233333
-
预热和长连接
其实这是嘴巴dudu园长大人在很久以前就做分析过 传送门
-
HttpClient 利用stream和json.net 减少内存开销,加快反序列化过程
我们一般的请求流程:发起请求,获取返回的string对象,然后反序列化成我们想要的对象。而其实可以利用stream直接反序列化成我们想要的对象。
而且可以是在HttpCompletionOption.ResponseHeadersRead的情况下。传送门
封印:
/// <summary> /// 异步http请求者。 /// 注意:杜绝new的形式,可以IOC容器单例注入 /// 如果多个address地址,通过name key形式注入多个单例 /// </summary> public class HttpAsyncSender { private readonly ILogger _logger = LoggerManager.GetLogger(typeof(HttpAsyncSender)); //静态 重用tcp连接 长连接(not dispose)https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ private readonly HttpClient _httpClient = new HttpClient(); public HttpAsyncSender(Uri baseUri, int timeoutSeconds = 0, bool keepAlive = true, bool preheating = false, long maxResponseContentBufferSize = 0) { //基础地址 _httpClient.BaseAddress = baseUri; //超时 if (timeoutSeconds != 0) { _httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds); } //response最大接收字节 默认2gbmstsc if (maxResponseContentBufferSize != 0) { _httpClient.MaxResponseContentBufferSize = maxResponseContentBufferSize; } //长连接 //https://www.cnblogs.com/lori/p/7692152.html http 1.1 default set keep alive if (keepAlive) { _httpClient.DefaultRequestHeaders.Connection.Add("keep-alive"); } //httpclient 预热 the first request //https://www.cnblogs.com/dudu/p/csharp-httpclient-attention.html if (preheating) { _httpClient.SendAsync(new HttpRequestMessage { Method = new HttpMethod("HEAD"), RequestUri = new Uri(baseUri + "/") }).Result.EnsureSuccessStatusCode(); } } #region 异步 /// <summary> /// GetAsync 注意 await always /// </summary> /// <param name="url"></param> /// <param name="cancellationToken"></param> /// <param name="jsonSerializerSettings"></param> /// <returns></returns> public async Task<T> GetAsync<T>(string url, CancellationToken cancellationToken, JsonSerializerSettings jsonSerializerSettings = null) { try { //https://johnthiriet.com/efficient-api-calls/ 减少内存开销 利用stream特性 加快反序列化 using (var request = new HttpRequestMessage(HttpMethod.Get, url)) { var res = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var resStesam = await res.Content.ReadAsStreamAsync().ConfigureAwait(false); if (res.IsSuccessStatusCode) { return DeserializeJsonFromStream<T>(resStesam, jsonSerializerSettings); } var resStr = await StreamToStringAsync(resStesam).ConfigureAwait(false); _logger.Error($"HttpAsyncSender, GetAsync ,response fail StatusCode:{res.StatusCode} resStr:{resStr} BaseAddress:{_httpClient.BaseAddress},Url:{url}"); } } catch (AggregateException ae) { _logger.Error($"HttpAsyncSender,GetAsync AggregateException,BaseAddress:{_httpClient.BaseAddress},Url:{url} ae:{ae.Flatten()}"); throw; } catch (Exception e) { _logger.Error($"HttpAsyncSender,GetAsync Exception,BaseAddress:{_httpClient.BaseAddress},Url:{url}",e); throw; } return default(T); } /// <summary> /// PostAsync 注意 await always /// </summary> /// <param name="url"></param> /// <param name="content"></param> /// <param name="cancellationToken"></param> /// <param name="jsonSerializerSettings"></param> /// <returns></returns> public async Task<TRes> PostAsync<TReq, TRes>(string url, TReq content, CancellationToken cancellationToken, JsonSerializerSettings jsonSerializerSettings = null) { try { //https://johnthiriet.com/efficient-api-calls/ 减少内存开销 利用stream特性 加快反序列化 using (var request = new HttpRequestMessage(HttpMethod.Post, url)) using (var httpContent = CreateHttpContent(content, jsonSerializerSettings)) { request.Content = httpContent; var res = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var resStesam = await res.Content.ReadAsStreamAsync().ConfigureAwait(false); if (res.IsSuccessStatusCode) { return DeserializeJsonFromStream<TRes>(resStesam, jsonSerializerSettings); } var resStr = await StreamToStringAsync(resStesam).ConfigureAwait(false); _logger.Error($"HttpAsyncSender, PostAsync ,response fail StatusCode:{res.StatusCode} resStr:{resStr} BaseAddress:{_httpClient.BaseAddress},Url:{url}"); } } catch (AggregateException ae) { _logger.Error($"HttpAsyncSender,PostAsync AggregateException,BaseAddress:{_httpClient.BaseAddress},Url:{url} ae:{ae.Flatten()}"); throw; } catch (Exception e) { _logger.Error($"HttpAsyncSender,PostAsync Exception,BaseAddress:{_httpClient.BaseAddress},Url:{url}",e); throw; } return default(TRes); } /// <summary> /// SendAsync 注意 await always 当需要动态改变request head的时候 调用此方法。 解决 "集合已修改;可能无法执行枚举操作" /// </summary> /// <param name="httpRequestMessage"></param> /// <param name="completionOption"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequestMessage, HttpCompletionOption completionOption, CancellationToken cancellationToken) { try { return await _httpClient.SendAsync(httpRequestMessage, completionOption, cancellationToken).ConfigureAwait(false); } catch (AggregateException ae) { _logger.Error( $"HttpAsyncSender,SendAsync AggregateException,BaseAddress:{_httpClient.BaseAddress},Url:{httpRequestMessage.RequestUri} ae:{ae.Flatten()}"); throw; } catch (Exception e) { _logger.Error($"HttpAsyncSender,SendAsync Exception,BaseAddress:{_httpClient.BaseAddress},Url:{httpRequestMessage.RequestUri}",e); throw; } } #endregion private T DeserializeJsonFromStream<T>(Stream stream, JsonSerializerSettings jsonSerializerSettings = null) { if (stream == null || stream.CanRead == false) return default(T); using (var sr = new StreamReader(stream)) using (var jtr = new JsonTextReader(sr)) { var js = jsonSerializerSettings == null ? new JsonSerializer() : JsonSerializer.Create(jsonSerializerSettings); var searchResult = js.Deserialize<T>(jtr); return searchResult; } } private async Task<string> StreamToStringAsync(Stream stream) { string content = null; if (stream != null) using (var sr = new StreamReader(stream)) content = await sr.ReadToEndAsync(); return content; } public void SerializeJsonIntoStream(object value, Stream stream, JsonSerializerSettings jsonSerializerSettings = null) { using (var sw = new StreamWriter(stream, new UTF8Encoding(false), 1024, true)) using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.None }) { var js = jsonSerializerSettings==null?new JsonSerializer():JsonSerializer.Create(jsonSerializerSettings); js.Serialize(jtw, value); jtw.Flush(); } } private HttpContent CreateHttpContent<T>(T content, JsonSerializerSettings jsonSerializerSettings = null) { HttpContent httpContent = null; if (content != null) { var ms = new MemoryStream(); SerializeJsonIntoStream(content, ms, jsonSerializerSettings); ms.Seek(0, SeekOrigin.Begin); httpContent = new StreamContent(ms); httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } return httpContent; } }