9102年了,彙總下HttpClient問題,封印一個

若是找的是core的HttpClientFactory 出門右轉。html

  1. 官方寫法,高併發下,TCP鏈接不能快速釋放,致使端口占完,沒法鏈接

    Dispose 不是立刻關閉tcp鏈接git

    主動關閉的一方爲何不能立刻close而是進入timewait狀態:TCP四次揮手客戶端關閉連接爲何要等待2倍MSLgithub

    正確寫法一個域(一個地址) 保證一個靜態httpclient操做,保證重用tcp鏈接。json

     

  2. 若是HttpClient惟一,若是請求頭內容須要變化怎麼辦,異常:"集合已修改;可能沒法執行枚舉操做"

    HttpClient有個接口SendAsync。看源碼知道其實HttpClient內部get,post,put,delete最終都是調用SendAsync。api

    這個方法能夠容許用戶傳遞HttpRequestMessage,內部包含(HttpRequestHeaders)併發

     

  3. HttpClient 死鎖

    關於Task同步上下文 形成死鎖問題就很少解釋。避免方法就是ConfigureAwait(false)或者await always。最好是await always。傳送門app

    說下不用await 而使用相似HttpClient.GetStringAsync(uri).Result 直接同步獲取爲何沒有死鎖異步

    由於HttpClient源碼裏面用到async await的地方几乎都加了ConfigureAwait(false)。233333async

     

  4. 預熱和長鏈接

    其實這是嘴巴dudu園長大人在好久之前就作分析過 傳送門tcp

     

  5. 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;
            }
        }
    View Code
相關文章
相關標籤/搜索