可以注册 IHttpClientFactory 并将其用于配置和创建应用中的 HttpClient 实例。 这能带来以下好处:
- 提供一个中心位置,用于命名和配置逻辑
HttpClient
实例。 例如,可以注册 github 客户端,并将它配置为访问 GitHub。 可以注册一个默认客户端用于其他用途。 - 通过委托
HttpClient
中的处理程序整理出站中间件的概念,并提供适用于基于 Polly 的中间件的扩展来利用概念。 - 管理基础
HttpClientMessageHandler
实例的池和生存期,避免在手动管理HttpClient
生存期时出现常见的 DNS 问题。 - (通过
ILogger
)添加可配置的记录体验,以处理工厂创建的客户端发送的所有请求。
在应用中可以通过以下多种方式使用 IHttpClientFactory
基本用法
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddHttpClient(); }
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private readonly IHttpClientFactory _clientFactory; public ValuesController(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } // GET api/values [HttpGet] public async Task<string> Get() { HttpClient client = _clientFactory.CreateClient(); //方法一: //HttpRequestMessage request = new HttpRequestMessage //{ // Method = new HttpMethod("get"), // RequestUri = new System.Uri("http://localhost:5000/api/values"), //}; //HttpResponseMessage response = await client.SendAsync(request); //string res = await response.Content.ReadAsStringAsync(); //return res; //方法二: string res = await client.GetStringAsync("http://localhost:5000/api/values"); return res; } }
命名客户端
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddHttpClient("test", c => { c.BaseAddress = new Uri("http://localhost:5000"); }); }
public async Task<string> Get() { HttpClient client = _clientFactory.CreateClient("test"); //注册名叫 "test" 的客户端时,已经指定了该客户端的请求基地址,所以这里不需要指定主机名了 return await client.GetStringAsync("api/values"); }
类型化客户端
public class TestHttpClient { public HttpClient Client { get; set; } public TestHttpClient(HttpClient client) { client.BaseAddress = new System.Uri("http://localhost:5000"); Client = client; } public async Task<string> Get() { return await Client.GetStringAsync("api/values"); } }
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddHttpClient<TestHttpClient>(c => { //可以在这里设置,也可以在构造函数设置. //c.BaseAddress = new System.Uri("http://localhost:5000"); }); }
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private readonly TestHttpClient _client; public ValuesController(TestHttpClient client) { _client = client; } [HttpGet] public async Task<string> Get() { return await _client.Get(); } }
出站请求中间件
HttpClient
已经具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站 HTTP 请求。 IHttpClientFactory
可以轻松定义处理程序并应用于每个命名客户端。 它支持注册和链接多个处理程序,以生成出站请求中间件管道。 每个处理程序都可以在出站请求前后执行工作。 此模式类似于 ASP.NET Core 中的入站中间件管道。 它提供了一种用于管理围绕 HTTP 请求的横切关注点的机制,包括缓存、错误处理、序列化以及日志记录。
要创建处理程序,需要定义一个派生自 DelegatingHandler
的类。 重写 SendAsync
方法,在将请求传递至管道中的下一个处理程序之前执行代码:
public class ValidateHeaderHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (!request.Headers.Contains("refuge")) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("not found refuge") }; } return await base.SendAsync(request, cancellationToken); } }
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddTransient<ValidateHeaderHandler>();//生存期必须是临时 services.AddHttpClient("test", c => { c.BaseAddress = new Uri("http://localhost:5000"); }) .AddHttpMessageHandler<ValidateHeaderHandler>(); }
HttpClient 和生存期管理
每次对 IHttpClientFactory
调用 CreateClient
都会返回一个新 HttpClient
实例:
public IEnumerable<int> Get() { //测试生存期 for (int i = 0; i < 4; i++) { HttpClient client = i % 2 == 0 ? _clientFactory.CreateClient("test") : _clientFactory.CreateClient(); yield return client.GetHashCode(); } }
CreateClient 方法内部会调用 CreateHandler 方法,后者创建 HttpMessageHandler,
源码如下:
public HttpClient CreateClient(string name) { if (name == null) throw new ArgumentNullException(nameof (name)); HttpClient httpClient = new HttpClient(this.CreateHandler(name), false); HttpClientFactoryOptions clientFactoryOptions = this._optionsMonitor.Get(name); for (int index = 0; index < clientFactoryOptions.HttpClientActions.Count; ++index) clientFactoryOptions.HttpClientActions[index](httpClient); return httpClient; }
public HttpMessageHandler CreateHandler(string name) { if (name == null) throw new ArgumentNullException(nameof (name)); ActiveHandlerTrackingEntry entry = this._activeHandlers.GetOrAdd(name, this._entryFactory).Value; this.StartHandlerEntryTimer(entry); return (HttpMessageHandler) entry.Handler; }
而这个 _activeHandlers 的类型是 :
一个线程安全的键值对集合.
因此,实际上创建的 HttpMessageHandler 实例会汇集到池中.新建 HttpClient
实例时,可能会重用池中的 HttpMessageHandler
实例(如果生存期尚未到期的话).
由于每个处理程序通常管理自己的基础 HTTP 连接,因此需要池化处理程序.创建超出必要数量的处理程序可能会导致连接延迟. 部分处理程序还保持连接无期限地打开,这样可以防止处理程序对 DNS 更改作出反应.
处理程序的默认生存期为两分钟,可在每个命名客户端上重写默认值:
services.AddHttpClient("test").SetHandlerLifetime(TimeSpan.FromMinutes(5));
配置 HttpMessageHandler
有时候,我们需要控制客户端使用的内部 HttpMessageHandler
.
services.AddHttpClient("test") .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { AllowAutoRedirect = false, });