9102年了,汇总下HttpClient问题,封印一个
如果找的是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 = , bool keepAlive = true, bool preheating = false, long maxResponseContentBufferSize = )
- {
- //基础地址
- _httpClient.BaseAddress = baseUri;
- //超时
- if (timeoutSeconds != )
- {
- _httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
- }
- //response最大接收字节 默认2gbmstsc
- if (maxResponseContentBufferSize != )
- {
- _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), , 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(, SeekOrigin.Begin);
- httpContent = new StreamContent(ms);
- httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
- }
- return httpContent;
- }
- }
- /// <summary>
9102年了,汇总下HttpClient问题,封印一个的更多相关文章
- Cocos开发中Visual Studio下HttpClient开发环境设置
Cocos2d-x 3.x将与网络通信相关的类集成到libNetwork类库工程中,这其中包括了HttpClient类.我们需要在Visual Studio解决方案中添加libNetwork类库工程. ...
- .Net45下HttpClient的几个缺陷
前言 最近在写WebClientApi这个组件,底层使用HttpClient,发现HttpClient有许多低级的错误,使用者一不小心就可能会正常的去调用它的这些错误,得不到预期的结果.本文我把我认为 ...
- Swift技术之如何在iOS 8下使用Swift设计一个自定义的输入法 (主要是NSLayoutConstraint 的使用)
当前位置: > Swift新手入门 > Swift技术之如何在iOS 8下使用Swift设计一个自定义的输入法 时间:2014-09-10 16:49来源:未知 作者:啊成 举报 点击:5 ...
- Eclipse 下如何引用另一个项目的Java文件
有关联的2个项目,有些类是相同的.例如实体类. 如果你采用 Ctrl + C & Ctrl + V 的方式,以后再有改动,2个项目就都需要改动. 怎样才能只改动一个呢? 答案就是,在一个项目( ...
- Eclipse 下如何引用另一个项目的资源文件
为什么要这么做?可参考:Eclipse 下如何引用另一个项目的Java文件 下面直接说下步骤:(项目A 引用 项目B的资源文件) 1.右键 项目A,点击菜单 Properties 2.在弹出的框中,点 ...
- Qt ------ window下工程项目打包成一个exe程序
最近,在学习QT5的过程中,想尝试着把自己写的工程程序给打包发布出来,在任何一台windows系统都能运行,这样就不会限于电脑需不需要安装QT安装包了. 首先,先介绍自己使用的环境.我使用的QT版本是 ...
- 在Linux下,如何分析一个程序达到性能瓶颈的原因
0.在Linux下,如何分析一个程序达到性能瓶颈的原因,请分别从CPU.内存.IO.网络的角度判断是谁导致的瓶颈?注意现在的机器CPU是多核 1.用sar -n DEV 1 10 2.用iotop命令 ...
- 【小程序】微信小程序打开其他小程序(打开同一主体公众号下关联的另一个小程序)
微信小程序打开其他小程序(打开同一公众号下关联的另一个小程序) 注:只有同一(主体)公众号下的关联的小程序之间才可相互跳转 wx.navigateToMiniProgram(OBJECT) wx.n ...
- PyCharm在同一个包(package)下,如何把一个.py文件导入另外一个.py文件下
PyCharm在同一个包(package)下,如何把一个.py文件导入另外一个.py文件下 在同一个包下只需要用import 掉以后就可以找到模块所在的位置,但是如果不在同一个包下,在需要返回父级调用 ...
随机推荐
- [二分答案][NOIP2015]跳石头
跳石头 题目描述 一年一度的“跳石头”比赛又要开始了!这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 N 块岩石(不含起 ...
- get通配符
这篇文章是在上一篇常用正则表达式(合) https://blog.csdn.net/yeyeye200/article/details/86186889 基础上的延伸:关于通配符的使用~ 一开始get ...
- idea debug快捷键 快速查找类
快速查找类或者文件比如xml .txt Ctrl + Shift + N 快速查找类 双击Shift 选中代码右移 Tab 选中代码左移 Shift + Tab 选中代码上下移 Shift + Alt ...
- 【机器学习】主成分分析法 PCA (II)
主成分分析法(PAC)的优化——选择主成分的数量 根据上一讲,我们知道协方差为① 而训练集的方差为②. 我们希望在方差尽可能小的情况下选择尽可能小的K值. 也就是说我们需要找到k值使得①/②的值尽可能 ...
- VMware Workstation:安装windows xp系统
https://blog.csdn.net/nicergj/article/details/83651603
- uiautomatorviewer报错“Error taking device screenshot: EOF” ,
uiautomatorviewer报错“Error taking device screenshot: EOF” ,千万不要装手机助手,不要装手机助手,不要装手机助手 uiautomatorview ...
- Fiddler抓包工具安装与使用
1.Fiddler简介2.Fiddler安装步骤3.Fiddler目录结构4.Fiddler证书配置5.Fiddler录制配置6.Fiddler工作原理7.Fiddler界面详解 1.Fiddler简 ...
- git遇到的问题-- Another git process seems to be running in this repository
执行git add .时,报错 fatal: Unable to create '/Users/lily/ForWork/forReBaomai/bm-fe/.git/index.lock': Fil ...
- Cookie的使用(14)
一:cookie的简要介绍: (1)什么是cookie a.cookie是一种客户端的状态管理技术b.当浏览器向服务器发送请求的时候,服务器会将少量的数据以set-cookie消息头的方式发送给浏览器 ...
- 多个子域名前端网站调用同一个webAPI时session混用问题
session机制: 当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个sess ...