zoukankan      html  css  js  c++  java
  • 9102年了,汇总下HttpClient问题,封印一个

    如果找的是core的HttpClientFactory 出门右转。

    1. 官方写法,高并发下,TCP连接不能快速释放,导致端口占完,无法连接

      Dispose 不是马上关闭tcp连接

      主动关闭的一方为什么不能马上close而是进入timewait状态:TCP四次挥手客户端关闭链接为什么要等待2倍MSL

      正确写法一个域(一个地址) 保证一个静态httpclient操作,保证重用tcp连接。

    2. 如果HttpClient唯一,如果请求头内容需要变化怎么办,异常:"集合已修改;可能无法执行枚举操作"

      HttpClient有个接口SendAsync。看源码知道其实HttpClient内部get,post,put,delete最终都是调用SendAsync。

      这个方法可以允许用户传递HttpRequestMessage,内部包含(HttpRequestHeaders)

    3. HttpClient 死锁

      关于Task同步上下文 造成死锁问题就不多解释。避免方法就是ConfigureAwait(false)或者await always。最好是await always。传送门

      说下不用await 而使用类似HttpClient.GetStringAsync(uri).Result 直接同步获取为什么没有死锁

      因为HttpClient源码里面用到async await的地方几乎都加了ConfigureAwait(false)。233333

    4. 预热和长连接

      其实这是嘴巴dudu园长大人在很久以前就做分析过 传送门

    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
  • 相关阅读:
    Calling a parent window function from an iframe
    JSON with Java
    Posting array of JSON objects to MVC3 action method via jQuery ajax
    What's the difference between jquery.js and jquery.min.js?
    jquery loop on Json data using $.each
    jquery ui tabs详解(中文)
    DataTables warning requested unknown parameter
    Datatables 1.10.x在命名上与1.9.x
    jQuery 1.x and 2.x , which is better?
    DataTabless Add rows
  • 原文地址:https://www.cnblogs.com/TeemoHQ/p/10266330.html
Copyright © 2011-2022 走看看