HttpClient类旨在提供一个用户入口,其内部管理着不同服务器的TCP连接池,如下图所示:
所以,当我们需要发起http请求时,最好使用全局单例的HttpClient,而不是每次都new一个HttpClient。
另外,由于TCP本身在断开连接的时候需要4次挥手动作,而其中又有一个等待时间,所以我们即使将HttpClient.Dispose()掉也会造成这个TCP连接短时间内无法断开(最长要持续4分钟),如果遇到高并发的话,很可能端口就不够用了。正是因为这个原因,微软又出了一个IHttpClientFactory帮助我们建立可复用的HttpClient。
注意:上面缓存连接的时候是以传入的地址前缀做key,而不是最终解析的ip地址,所以,HttpClient对DNS解析不太友好。
HttpClient是线程安全的,里面封装了链接池,使用DnSpy验证如下:
当我们发送http请求时,我们需要关注一些事情,比如:
是否自动处理cookie;
默认HttpClient是自动处理cookie的,即:上一个请求返回的cookie,可能会随着下次请求发送出去。
然而,最佳的使用方式是多次请求使用相同的HttpClient所以这个cookie隔离性就很差,我们可以在创建HttpClient的时候进行配置禁用cookie自动处理:
var socketsHttpHandler = new SocketsHttpHandler()
UseCookies = false,// 是否自动处理cookie
var client = new HttpClient(socketsHttpHandler);
默认HttpClient自动处理重定向请求,并且最多重定向50次,一般我们不需要修改这个配置,但我们做测试的话,可以向下面写法:
var socketsHttpHandler = new SocketsHttpHandler()
AllowAutoRedirect=true,//是否自动重定向
MaxAutomaticRedirections=50//自动重定向的最大次数
var client = new HttpClient(socketsHttpHandler);
这个地方有三个配置项:
MaxConnectionsPerServer: 每个url(如:http://www.baidu.com:80)最多有几个链接,默认是int.MaxValue。注意:url是不带路径及参数;
PooledConnectionIdleTimeout: 每个TCP链接空闲的时间,因为TCP长时间不用也要及时释放嘛,此处默认2分钟;
PooledConnectionLifetime: 每个TCP链接从创建开始存活的时间,默认是不限制的,一般也不用设置这个参数;
直接看代码示例:
var socketsHttpHandler = new SocketsHttpHandler()
//每个请求连接的最大数量,默认是int.MaxValue,可以认为是不限制
MaxConnectionsPerServer = 100,
//连接池中TCP连接最多可以闲置多久,默认2分钟
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
//连接最长的存活时间,默认是不限制的,一般不用设置
PooledConnectionLifetime = Timeout.InfiniteTimeSpan,
var client = new HttpClient(socketsHttpHandler);
是否压缩;
MaxResponseHeadersLength: http响应头最大字节数(单位:KB),默认:64,即:http响应头最大64KB,一般不用设置
看代码设置:
关于BaseAddress的配置:可以给HttpClient设置基地址,当HttpClient发送的请求不包含前缀时,将自动拼接上,否则不予拼接,如下:
可以在这里面配置SSL相关的东西。这里我只实验了一种场景,即:访问https网站时由于网站自身的证书不规范导致报错:“AuthenticationException: The remote certificate is invalid according to the validation procedure.”
在我们使用HttpWebRequest的时候我们可以通过下面回调设置:
httpWebRequest.ServerCertificateValidationCallback = (sender, cer, chain, err) => true;
在HttpClient时,我们对应的设置为:
现在我们要下载一个文件,假如这个文件不超过2G且不需要下载进度提示,那么我们可以如下操作:
var httpClient = new HttpClient();
var response = await httpClient.GetAsync("http://localhost:9000/middledata.mp4");//763M
var fileStream = new FileStream("e:\\middle.db", FileMode.OpenOrCreate, FileAccess.Write);
await response.Content.CopyToAsync(fileStream);
fileStream.Close();
但考虑到下载的文件会过大,比如:3GB,这个时候首先HttpClient的缓冲区就不够用了,因为它最大设置的是:int.MaxValue=2^31-1≈2GB,看下面的报错代码:
var httpClient = new HttpClient();
//默认缓冲大小为: 2147483647=int.MaxValue=2^31-1≈2GB,如果下载的文件过大就会报异常: "Cannot write more bytes to the buffer than the configured maximum buffer size: 2147483647."
//可以手动设置缓冲区大小,但最大就是int.MaxValue,再大就会报错: "Buffering more than 2147483647 bytes is not supported."
httpClient.MaxResponseContentBufferSize = 2L << 32; //调成4GB,发现报错
这个时候我们就不能一次性获取Http响应报文的全部内容了,需要如下操作:
var httpClient = new HttpClient();
var url = "http://localhost:9000/bigdata.mp4";//3GB大小
//注意:因为太大,必须指定 HttpCompletionOption.ResponseHeadersRead,即:拿到响应头就返回
var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
var fileStream = new FileStream("e:\\bigdata.db", FileMode.OpenOrCreate, FileAccess.Write);
// 虽然上面指定拿到响应头就返回,但这里依然可以拿到下载的文件流
await response.Content.CopyToAsync(fileStream);
fileStream.Close();
现在,需要对这个大文件加上下载进度提示,我们需要事先获取文件的大小,这通过http响应头的Content-Length可以获取到(但并不总能获取到):
var httpClient = new HttpClient();
var url = "http://localhost:9000/bigdata.mp4";
var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
var totalLength = response.Content.Headers.ContentLength;
var contentStream = await response.Content.ReadAsStreamAsync();
var fileStream = new FileStream("e:\\bigdata.db", FileMode.OpenOrCreate, FileAccess.Write);
byte[] buffer = new byte[5 * 1024];//5KB缓存
long readLength = 0L;
int length;
while ((length = await contentStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
readLength += length;
if (totalLength > 0)
Console.WriteLine("下载进度: " + Math.Round((double)readLength / totalLength.Value * 100, 2) + "%");
Console.WriteLine("已下载: " + Math.Round((readLength / 1024.0), 2) + "KB");
fileStream.Write(buffer, 0, length);
fileStream.Close();
var httpClient = new HttpClient();
var url = "http://192.168.0.9:9000/Demo/PostUrlCode";
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
new KeyValuePair<string, string>("name","小明"),
new KeyValuePair<string, string>("age","20")
var str = await response.Content.ReadAsStringAsync();
上面的请求报文:
POST /Demo/PostUrlCode HTTP/1.1
Host: 192.168.0.9:9000
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
name=%E5%B0%8F%E6%98%8E&age=20
对应的asp.net core后台: