可以注冊(cè) IHttpClientFactory 并將其用于配置和創(chuàng)建應(yīng)用中的 HttpClient 實(shí)例。 這能帶來(lái)以下好處:
面向.NET Framework 的項(xiàng)目要求安裝 Microsoft.Extensions.Http NuGet 包。 面向 .NET Core 且引用 Microsoft.AspNetCore.App 元包的項(xiàng)目已經(jīng)包括 Microsoft.Extensions.Http 包。
在應(yīng)用中可以通過(guò)以下多種方式使用 IHttpClientFactory:
它們之間不存在嚴(yán)格的優(yōu)先級(jí)。 最佳方法取決于應(yīng)用的約束條件。
在 Startup.ConfigureServices 方法中,通過(guò)在 IServiceCollection 上調(diào)用 AddHttpClient 擴(kuò)展方法可以注冊(cè) IHttpClientFactory。
C#
services.AddHttpClient();
注冊(cè)后,在可以使用依賴(lài)關(guān)系注入 (DI) 注入服務(wù)的任何位置,代碼都能接受 IHttpClientFactory。 IHttpClientFactory 可以用于創(chuàng)建 HttpClient 實(shí)例:
C#
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/aspnet/docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
以這種方式使用 IHttpClientFactory 適合重構(gòu)現(xiàn)有應(yīng)用。 這不會(huì)影響 HttpClient 的使用方式。 在當(dāng)前創(chuàng)建 HttpClient 實(shí)例的位置,使用對(duì) CreateClient 的調(diào)用替換這些匹配項(xiàng)。
如果應(yīng)用需要有許多不同的 HttpClient 用法(每種用法的配置都不同),可以視情況使用命名客戶(hù)端。 可以在 HttpClient 中注冊(cè)時(shí)指定命名 Startup.ConfigureServices 的配置。
C#
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
上面的代碼調(diào)用 AddHttpClient,同時(shí)提供名稱(chēng)“github”。 此客戶(hù)端應(yīng)用了一些默認(rèn)配置,也就是需要基址和兩個(gè)標(biāo)頭來(lái)使用 GitHub API。
每次調(diào)用 CreateClient 時(shí),都會(huì)創(chuàng)建 HttpClient 的新實(shí)例,并調(diào)用配置操作。
要使用命名客戶(hù)端,可將字符串參數(shù)傳遞到 CreateClient。 指定要?jiǎng)?chuàng)建的客戶(hù)端的名稱(chēng):
C#
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/aspnet/docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
在上述代碼中,請(qǐng)求不需要指定主機(jī)名。 可以?xún)H傳遞路徑,因?yàn)椴捎昧藶榭蛻?hù)端配置的基址。
類(lèi)型化客戶(hù)端提供與命名客戶(hù)端一樣的功能,不需要將字符串用作密鑰。 類(lèi)型化客戶(hù)端方法在使用客戶(hù)端時(shí)提供 IntelliSense 和編譯器幫助。 它們提供單個(gè)地址來(lái)配置特定 HttpClient 并與其進(jìn)行交互。 例如,單個(gè)類(lèi)型化客戶(hù)端可能用于單個(gè)后端終結(jié)點(diǎn),并封裝此終結(jié)點(diǎn)的所有處理邏輯。 另一個(gè)優(yōu)勢(shì)是它們使用 DI 且可以被注入到應(yīng)用中需要的位置。
類(lèi)型化客戶(hù)端在構(gòu)造函數(shù)中接收 HttpClient 參數(shù):
C#
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/aspnet/docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<GitHubIssue>>();
return result;
}
}
在上述代碼中,配置轉(zhuǎn)移到了類(lèi)型化客戶(hù)端中。 HttpClient 對(duì)象公開(kāi)為公共屬性。 可以定義公開(kāi) HttpClient 功能的特定于 API 的方法。 GetAspNetDocsIssues 方法從 GitHub 存儲(chǔ)庫(kù)封裝查詢(xún)和分析最新待解決問(wèn)題所需的代碼。
要注冊(cè)類(lèi)型化客戶(hù)端,可在 Startup.ConfigureServices 中使用通用的 AddHttpClient 擴(kuò)展方法,指定類(lèi)型化客戶(hù)端類(lèi):
C#
services.AddHttpClient<GitHubService>();
使用 DI 將類(lèi)型客戶(hù)端注冊(cè)為暫時(shí)客戶(hù)端。 可以直接插入或使用類(lèi)型化客戶(hù)端:
C#
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
根據(jù)你的喜好,可以在 Startup.ConfigureServices 中注冊(cè)時(shí)指定類(lèi)型化客戶(hù)端的配置,而不是在類(lèi)型化客戶(hù)端的構(gòu)造函數(shù)中指定:
C#
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
可以將 HttpClient 完全封裝在類(lèi)型化客戶(hù)端中。 不是將它公開(kāi)為屬性,而是可以提供公共方法,用于在內(nèi)部調(diào)用 HttpClient。
C#
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<string>>();
return result;
}
}
在上述代碼中,HttpClient 存儲(chǔ)未私有字段。 進(jìn)行外部調(diào)用的所有訪(fǎng)問(wèn)都經(jīng)由 GetRepos 方法。
IHttpClientFactory 可結(jié)合其他第三方庫(kù)(例如 Refit)使用。 Refit 是.NET 的 REST 庫(kù)。 它將 REST API 轉(zhuǎn)換為實(shí)時(shí)接口。 RestService 動(dòng)態(tài)生成該接口的實(shí)現(xiàn),使用 HttpClient 進(jìn)行外部 HTTP 調(diào)用。
定義了接口和答復(fù)來(lái)代表外部 API 及其響應(yīng):
C#
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
可以添加類(lèi)型化客戶(hù)端,使用 Refit 生成實(shí)現(xiàn):
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddMvc();
}
可以在必要時(shí)使用定義的接口,以及由 DI 和 Refit 提供的實(shí)現(xiàn):
C#
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
HttpClient 已經(jīng)具有委托處理程序的概念,這些委托處理程序可以鏈接在一起,處理出站 HTTP 請(qǐng)求。 IHttpClientFactory 可以輕松定義處理程序并應(yīng)用于每個(gè)命名客戶(hù)端。 它支持注冊(cè)和鏈接多個(gè)處理程序,以生成出站請(qǐng)求中間件管道。 每個(gè)處理程序都可以在出站請(qǐng)求前后執(zhí)行工作。 此模式類(lèi)似于 ASP.NET Core 中的入站中間件管道。 此模式提供了一種用于管理圍繞 HTTP 請(qǐng)求的橫切關(guān)注點(diǎn)的機(jī)制,包括緩存、錯(cuò)誤處理、序列化以及日志記錄。
要?jiǎng)?chuàng)建處理程序,請(qǐng)定義一個(gè)派生自 DelegatingHandler 的類(lèi)。 重寫(xiě) SendAsync 方法,在將請(qǐng)求傳遞至管道中的下一個(gè)處理程序之前執(zhí)行代碼:
C#
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
上述代碼定義了基本處理程序。 它檢查請(qǐng)求中是否包含 X-API-KEY 頭。 如果標(biāo)頭缺失,它可以避免 HTTP 調(diào)用,并返回合適的響應(yīng)。
在注冊(cè)期間可將一個(gè)或多個(gè)標(biāo)頭添加到 HttpClient 的配置。 此任務(wù)通過(guò) IHttpClientBuilder 上的擴(kuò)展方法完成。
C#
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
在上述代碼中通過(guò) DI 注冊(cè)了 ValidateHeaderHandler。 IHttpClientFactory 為每個(gè)處理程序創(chuàng)建單獨(dú)的 DI 作用域。 處理程序可依賴(lài)于任何作用域的服務(wù)。 處理程序依賴(lài)的服務(wù)會(huì)在處置處理程序時(shí)得到處置。
注冊(cè)后可以調(diào)用 AddHttpMessageHandler,傳入標(biāo)頭的類(lèi)型。
可以按處理程序應(yīng)該執(zhí)行的順序注冊(cè)多個(gè)處理程序。 每個(gè)處理程序都會(huì)覆蓋下一個(gè)處理程序,直到最終 HttpClientHandler 執(zhí)行請(qǐng)求:
C#
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
使用以下方法之一將每個(gè)請(qǐng)求狀態(tài)與消息處理程序共享:
IHttpClientFactory 與一個(gè)名為 Polly 的熱門(mén)第三方庫(kù)集成。 Polly 是適用于 .NET 的全面恢復(fù)和臨時(shí)故障處理庫(kù)。 開(kāi)發(fā)人員通過(guò)它可以表達(dá)策略,例如以流暢且線(xiàn)程安全的方式處理重試、斷路器、超時(shí)、Bulkhead 隔離和回退。
提供了擴(kuò)展方法,以實(shí)現(xiàn)將 Polly 策略用于配置的 HttpClient 實(shí)例。Microsoft.Extensions.Http.Polly NuGet 包中提供 Polly 擴(kuò)展。 Microsoft.AspNetCore.App 元包中不包括此包。 若要使用擴(kuò)展,項(xiàng)目中應(yīng)該包括顯式 <PackageReference />。
C#
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.2.0" />
</ItemGroup>
</Project>
還原此包后,可以使用擴(kuò)展方法來(lái)支持將基于 Polly 的處理程序添加至客戶(hù)端。
大多數(shù)常見(jiàn)錯(cuò)誤在暫時(shí)執(zhí)行外部 HTTP 調(diào)用時(shí)發(fā)生。 包含了一種簡(jiǎn)便的擴(kuò)展方法,該方法名為 AddTransientHttpErrorPolicy,允許定義策略來(lái)處理臨時(shí)故障。 使用這種擴(kuò)展方法配置的策略可以處理 HttpRequestException、HTTP 5xx 響應(yīng)以及 HTTP 408 響應(yīng)。
AddTransientHttpErrorPolicy 擴(kuò)展可在 Startup.ConfigureServices 內(nèi)使用。 該擴(kuò)展可以提供 PolicyBuilder 對(duì)象的訪(fǎng)問(wèn)權(quán)限,該對(duì)象配置為處理表示可能的臨時(shí)故障的錯(cuò)誤:
C#
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
上述代碼中定義了 WaitAndRetryAsync 策略。 請(qǐng)求失敗后最多可以重試三次,每次嘗試間隔 600 ms。
存在其他擴(kuò)展方法,可以用于添加基于 Polly 的處理程序。 這類(lèi)擴(kuò)展的其中一個(gè)是 AddPolicyHandler,它具備多個(gè)重載。 一個(gè)重載允許在定義要應(yīng)用的策略時(shí)檢查該請(qǐng)求:
C#
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
在上述代碼中,如果出站請(qǐng)求為 GET,則應(yīng)用 10 秒超時(shí)。 其他所有 HTTP 方法應(yīng)用 30 秒超時(shí)。
嵌套 Polly 策略以增強(qiáng)功能是很常見(jiàn)的:
C#
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
在上述示例中,添加兩個(gè)處理程序。 第一個(gè)使用 AddTransientHttpErrorPolicy 擴(kuò)展添加重試策略。若請(qǐng)求失敗,最多可重試三次。 第二個(gè)調(diào)用 AddTransientHttpErrorPolicy 添加斷路器策略。 如果嘗試連續(xù)失敗了五次,則會(huì)阻止后續(xù)外部請(qǐng)求 30 秒。 斷路器策略處于監(jiān)控狀態(tài)。 通過(guò)此客戶(hù)端進(jìn)行的所有調(diào)用都共享同樣的線(xiàn)路狀態(tài)。
管理常用策略的一種方法是一次性定義它們并使用 PolicyRegistry 注冊(cè)它們。 提供了一種擴(kuò)展方法,可以使用注冊(cè)表中的策略添加處理程序:
C#
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
在上面的代碼中,兩個(gè)策略在 PolicyRegistry 添加到 ServiceCollection 中時(shí)進(jìn)行注冊(cè)。 若要使用注冊(cè)表中的策略,請(qǐng)使用 AddPolicyHandlerFromRegistry 方法,同時(shí)傳遞要應(yīng)用的策略的名稱(chēng)。
要進(jìn)一步了解 IHttpClientFactory 和 Polly 集成,請(qǐng)參考 Polly Wiki。
每次對(duì) IHttpClientFactory 調(diào)用 CreateClient 都會(huì)返回一個(gè)新 HttpClient 實(shí)例。 每個(gè)命名的客戶(hù)端都具有一個(gè) HttpMessageHandler。 工廠管理 HttpMessageHandler 實(shí)例的生存期。
IHttpClientFactory 將工廠創(chuàng)建的 HttpMessageHandler 實(shí)例匯集到池中,以減少資源消耗。 新建 HttpClient 實(shí)例時(shí),可能會(huì)重用池中的 HttpMessageHandler 實(shí)例(如果生存期尚未到期的話(huà))。
由于每個(gè)處理程序通常管理自己的基礎(chǔ) HTTP 連接,因此需要池化處理程序。 創(chuàng)建超出必要數(shù)量的處理程序可能會(huì)導(dǎo)致連接延遲。 部分處理程序還保持連接無(wú)期限地打開(kāi),這樣可以防止處理程序?qū)?DNS 更改作出反應(yīng)。
處理程序的默認(rèn)生存期為兩分鐘。 可在每個(gè)命名客戶(hù)端上重寫(xiě)默認(rèn)值。 要重寫(xiě)該值,請(qǐng)?jiān)趧?chuàng)建客戶(hù)端時(shí)在返回的 IHttpClientBuilder 上調(diào)用 SetHandlerLifetime:
C#
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
無(wú)需處置客戶(hù)端。 處置既取消傳出請(qǐng)求,又保證在調(diào)用 Dispose 后無(wú)法使用給定的 HttpClient 實(shí)例。 IHttpClientFactory 跟蹤和處置 HttpClient 實(shí)例使用的資源。 HttpClient 實(shí)例通??梢暈闊o(wú)需處置的 .NET 對(duì)象。
保持各個(gè) HttpClient 實(shí)例長(zhǎng)時(shí)間處于活動(dòng)狀態(tài)是在 IHttpClientFactory 推出前使用的常見(jiàn)模式。遷移到 IHttpClientFactory 后,就無(wú)需再使用此模式。
通過(guò) IHttpClientFactory 創(chuàng)建的客戶(hù)端記錄所有請(qǐng)求的日志消息。 在日志記錄配置中啟用合適的信息級(jí)別可以查看默認(rèn)日志消息。 僅在跟蹤級(jí)別包含附加日志記錄(例如請(qǐng)求標(biāo)頭的日志記錄)。
用于每個(gè)客戶(hù)端的日志類(lèi)別包含客戶(hù)端名稱(chēng)。 例如,名為“MyNamedClient”的客戶(hù)端使用 System.Net.Http.HttpClient.MyNamedClient.LogicalHandler 類(lèi)別來(lái)記錄消息。 后綴為 LogicalHandler 的消息在請(qǐng)求處理程序管道外部發(fā)生。 在請(qǐng)求時(shí),在管道中的任何其他處理程序處理請(qǐng)求之前記錄消息。 在響應(yīng)時(shí),在任何其他管道處理程序接收響應(yīng)之后記錄消息。
日志記錄還在請(qǐng)求處理程序管道內(nèi)部發(fā)生。 在“MyNamedClient”示例中,這些消息是針對(duì)日志類(lèi)別 System.Net.Http.HttpClient.MyNamedClient.ClientHandler 進(jìn)行記錄。 在請(qǐng)求時(shí),在所有其他處理程序運(yùn)行后,以及剛好在通過(guò)網(wǎng)絡(luò)發(fā)出請(qǐng)求之前記錄消息。 在響應(yīng)時(shí),此日志記錄包含響應(yīng)在通過(guò)處理程序管道被傳遞回去之前的狀態(tài)。
在管道內(nèi)外啟用日志記錄,可以檢查其他管道處理程序做出的更改。 例如,其中可能包含對(duì)請(qǐng)求標(biāo)頭的更改,或者對(duì)響應(yīng)狀態(tài)代碼的更改。
通過(guò)在日志類(lèi)別中包含客戶(hù)端名稱(chēng),可以在必要時(shí)對(duì)特定的命名客戶(hù)端篩選日志。
控制客戶(hù)端使用的內(nèi)部 HttpMessageHandler 的配置是有必要的。
在添加命名客戶(hù)端或類(lèi)型化客戶(hù)端時(shí),會(huì)返回 IHttpClientBuilder。ConfigurePrimaryHttpMessageHandler 擴(kuò)展方法可以用于定義委托。 委托用于創(chuàng)建和配置客戶(hù)端使用的主要 HttpMessageHandler:
C#
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
更多建議: