一、HTTP系列演进
方式 | 说明 |
---|---|
HttpWebRequest | .NET早期版本,同步方式 |
WebClient | HttpWebRequest的封装简化版,同步方式 |
HttpClient | .NET4.5以后,异步方式 |
HttpClientFactory | .NET Core2.1 |
二、HttpClient用法
HttpClient 提供的方法:
GetAsync(String) //以异步操作将GET请求发送给指定的URI
GetAsync(URI) //以异步操作将GET请求发送给指定的URI
GetAsync(String, HttpCompletionOption) //以异步操作的HTTP完成选项发送GET请求到指定的URI
GetAsync(String, CancellationToken) //以异步操作的取消标记发送GET请求到指定URI
GetAsync(Uri, HttpCompletionOption) //以异步操作的HTTP完成选项发送GET请求到指定的URI
GetAsync(Uri, HttpCompletionOption, CancellationToken) //以异步操作的HTTP完成选项和取消标记发送DELETE请求到指定的URI
GetAsync(Uri, HttpCompletionOption, CancellationToken) //以异步操作的HTTP完成选项和取消标记发送DELETE请求到指定的URI
GetByteArrayAsync(String) //将GET请求发送到指定URI并在异步操作中以字节数组的形式返回响应正文
GetByteArrayAsync(Uri) //将GET请求发送到指定URI并在一异步操作中以字节数组形式返回响应正文
GetHashCode //用作特定类型的哈希函数,继承自Object
GetStreamAsync(String) //将GET请求发送到指定URI并在异步操作中以流的形式返回响应正文
GetStreamAsync(Uri) //将GET请求发送到指定URI并在异步操作以流的形式返回响应正文
GetStreamAsync(String) //将GET请求发送到指定URI并在异步操作中以字符串的形式返回响应正文
GetStringAsync(Uri) //将GET请求发送到指定URI并在异步操作中以字符串形式返回响应正文
using(var httpClient = new HttpClient())
{
//other codes
}
以上用法是不推荐的,HttpClient 这个对象有点特殊,虽然继承了 IDisposable 接口,但它是可以被共享的(或者说可以被复用),且线程安全。从项目经验来看,推荐在整个应用的生命周期内复用 HttpClient 实例,而不是每次RPC请求的时候就实例化一个,在高并发的情况下,会造成Socket资源的耗尽。
示例1:
public class Program
{
private static readonly HttpClient _httpClient = new HttpClient();
static void Main(string[] args)
{
HttpAsync();
Console.WriteLine("Hello World!");
Console.Read();
}
public static async void HttpAsync()
{
for (int i = 0; i < 10; i++)
{
var result = await _httpClient.GetAsync("http://www.baidu.com");
Console.WriteLine($"{i}:{result.StatusCode}");
}
}
}
示例2:
public async Task<string> GetAccessTokenAsync()
{
string uri = "你的URL";
HttpClientHandler handler = new HttpClientHandler
{
//设置是否发送凭证信息,有的服务器需要验证身份,不是所有服务器需要
UseDefaultCredentials = false
};
HttpClient httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.GetAsync(uri);
response.EnsureSuccessStatusCode();
//回复结果直接读成字符串
string resp = await response.Content.ReadAsStringAsync();
JObject json = (JObject)JsonConvert.DeserializeObject(resp);
string accessToken = json["access_token"].ToString();
//采用流读数据
//using (Stream streamResponse = await response.Content.ReadAsStreamAsync())
//{
// StreamReader reader = new StreamReader(streamResponse);
// string responseFromServer = reader.ReadToEnd();
// JObject res = (JObject)JsonConvert.DeserializeObject(responseFromServer);
// accessToken = res["access_token"].ToString();
// reader.Close();
//}
//获得许可证凭证
PostMailAsync(accessToken);
//关闭响应
return "success";
}
优化:帮HttpClient预热
我们采用一种预热方式,在正式发post请求之前,先发一个head请求:
_httpClient.SendAsync(new HttpRequestMessage {
Method = new HttpMethod("HEAD"),
RequestUri = new Uri(BASE_ADDRESS + "/")
})
.Result.EnsureSuccessStatusCode();
经测试,通过这种热身方法,可以将第一次请求的耗时由2s左右降到1s以内(测试结果是700多ms)。
存在问题
复用 HttpClient 后,依然存在一些问题:
- 因为是复用的 HttpClient ,那么一些公共的设置就没办法灵活的调整了,如请求头的自定义。
- 因为 HttpClient 请求每个 url 时,会缓存该 url 对应的主机 ip ,从而会导致 DNS 更新失效( TTL 失效了)
那么有没有办法解决HttpClient的这些个问题?直到 HttpClientFactory 的出现,这些坑 “完美” 规避掉了。
三、HttpClientFactory
- HttpClientFacotry 很高效,可以最大程度上节省系统 socket 。(“JUST USE IT AND FXXK SHUT UP”😛)
- Factory,顾名思义 HttpClientFactory 就是 HttpClient 的工厂,内部已经帮我们处理好了对 HttpClient 的管理,不需要我们人工进行对象释放,同时,支持自定义请求头,支持DNS更新等等等。
- 从微软源码分析,HttpClient 继承自 HttpMessageInvoker ,而 HttpMessageInvoker 实质就是 HttpClientHandler 。
- HttpClientFactory 创建的 HttpClient ,也即是 HttpClientHandler ,这些个 HttpClient 被放到了“池子”中,工厂每次在 create 的时候会自动判断是新建还是复用。(默认生命周期为 2 min)
示例1
- 在 Startup.cs 中进行注册
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//other codes
services.AddHttpClient("client_1", config => //这里指定的 name=client_1 ,可以方便我们后期复用该实例
{
config.BaseAddress = new Uri("http://client_1.com");
config.DefaultRequestHeaders.Add("header_1", "header_1");
});
services.AddHttpClient("client_2", config =>
{
config.BaseAddress = new Uri("http://client_2.com");
config.DefaultRequestHeaders.Add("header_2", "header_2");
});
services.AddHttpClient();
//other codes
services.AddMvc().AddFluentValidation();
}
}
- 使用,这里直接以 controller 为例,其他地方自行 DI
public class TestController : ControllerBase
{
private readonly IHttpClientFactory _httpClient;
public TestController(IHttpClientFactory httpClient)
{
_httpClient = httpClient;
}
public async Task<ActionResult> Test()
{
var client = _httpClient.CreateClient("client_1"); //复用在 Startup 中定义的 client_1 的 httpclient
var result = await client.GetStringAsync("/page1.html");
var client2 = _httpClient.CreateClient(); //新建一个 HttpClient
var result2 = await client.GetStringAsync("http://www.site.com/XXX.html");
return null;
}
}
示例2:使用自定义类执行 HttpClientFactory 请求
- 自定义 HttpClientFactory 请求类
public class SampleClient
{
public HttpClient Client { get; private set; }
public SampleClient(HttpClient httpClient)
{
httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
Client = httpClient;
}
}
- 在 Startup.cs 中 ConfigureService 方法中注册 SampleClient
services.AddHttpClient<SampleClient>();
- 调用:
public class ValuesController : Controller
{
private readonly SampleClient _sampleClient;
public ValuesController(SampleClient sampleClient)
{
_sampleClient = sampleClient;
}
[HttpGet]
public async Task<ActionResult> Get()
{
string result = await _sampleClient.client.GetStringAsync("/");
return Ok(result);
}
}
示例3:完全封装 HttpClient 可以使用下面方法
- 自定义 HttpClientFactory 请求类
public interface ISampleClient
{
Task<string> GetData();
}
public class SampleClient : ISampleClient
{
private readonly HttpClient _client;
public SampleClient(HttpClient httpClient)
{
httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
_client = httpClient;
}
public async Task<string> GetData()
{
return await _client.GetStringAsync("/");
}
}
- 在Startup.cs中ConfigureService方法中注册SampleClient
services.AddHttpClient<ISampleClient, SampleClient>();
- 调用:
public class ValuesController : Controller
{
private readonly ISampleClient _sampleClient;
public ValuesController(ISampleClient sampleClient)
{
_sampleClient = sampleClient;
}
[HttpGet]
public async Task<ActionResult> Get()
{
string result = await _sampleClient.GetData();
return Ok(result);
}
}