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
  • 相关阅读:
    互联网公司笔试常见陷阱
    华为机试题(地铁换乘,图的应用)
    容器(vector)、数组、new创建的动态数组,你到底用哪一个(执行效率分析)
    SpringMVC全局异常
    github commit contributions无记录
    elasticSearch之查询
    nginx负载均衡策略
    docker入门
    解决centos&win安装安装elasticSearch无法运行
    2017年终总结
  • 原文地址:https://www.cnblogs.com/TeemoHQ/p/10266330.html
Copyright © 2011-2022 走看看