ASP.NET Core基礎知識(十四)【發出 HTTP 請求】
可以註冊 IHttpClientFactory
並將其用於配置和建立應用中的 HttpClient
例項。 這能帶來以下好處:
- 提供一箇中心位置,用於命名和配置邏輯
HttpClient
例項。 例如,可以註冊 github 客戶端,並將它配置為訪問 GitHub。 可以註冊一個預設客戶端用於其他用途。 - 通過委託
HttpClient
中的處理程式整理出站中介軟體的概念,並提供適用於基於Polly
的中介軟體的擴充套件來利用概念。 - 管理基礎
HttpClientMessageHandler
例項的池和生存期,避免在手動管理HttpClient
生存期時出現常見的 DNS 問題。 - (通過
ILogger
)新增可配置的記錄體驗,以處理工廠建立的客戶端傳送的所有請求。
系統必備
面向.NET Framework 的專案要求安裝 Microsoft.Extensions.Http NuGet 包。 面向 .NET Core 且引用 Microsoft.AspNetCore.App
元包的專案已經包括 Microsoft.Extensions.Http
包。
消耗模式
在應用中可以通過以下多種方式使用 IHttpClientFactory
:
- 基本用法
- 命名客戶端
- 型別化客戶端
- 生成的客戶端
它們之間不存在嚴格的優先順序。 最佳方法取決於應用的約束條件。
基本用法
在 Startup.ConfigureServices
方法中,通過在 IServiceCollection
上呼叫 AddHttpClient
擴充套件方法可以註冊 IHttpClientFactory
。
services.AddHttpClient();
註冊後,在可以使用依賴關係注入 (DI)
注入服務的任何位置,程式碼都能接受 IHttpClientFactory
。 IHttpClientFactory
可以用於建立 HttpClient
例項:
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
適合重構現有應用。 這不會影響 HttpClient
的使用方式。 在當前建立 HttpClient
例項的位置,使用對 CreateClient
的呼叫替換這些匹配項。
命名客戶端
如果應用需要有許多不同的 HttpClient
用法(每種用法的配置都不同),可以視情況使用命名客戶端。 可以在 HttpClient
中註冊時指定命名 Startup.ConfigureServices
的配置。
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");
});
上面的程式碼呼叫 AddHttpClient
,同時提供名稱“github”。 此客戶端應用了一些預設配置,也就是需要基址和兩個標頭來使用 GitHub API。
每次呼叫 CreateClient
時,都會建立 HttpClient
的新例項,並呼叫配置操作。
要使用命名客戶端,可將字串引數傳遞到 CreateClient
。 指定要建立的客戶端的名稱:
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>();
}
}
}
在上述程式碼中,請求不需要指定主機名。 可以僅傳遞路徑,因為採用了為客戶端配置的基址。
型別化客戶端
型別化客戶端提供與命名客戶端一樣的功能,不需要將字串用作金鑰。 型別化客戶端方法在使用客戶端時提供 IntelliSense 和編譯器幫助。 它們提供單個地址來配置特定 HttpClient
並與其進行互動。 例如,單個型別化客戶端可能用於單個後端終結點,並封裝此終結點的所有處理邏輯。 另一個優勢是它們使用 DI 且可以被注入到應用中需要的位置。
型別化客戶端在建構函式中接收 HttpClient
引數:
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;
}
}
在上述程式碼中,配置轉移到了型別化客戶端中。 HttpClient
物件公開為公共屬性。 可以定義公開 HttpClient
功能的特定於 API 的方法。 GetAspNetDocsIssues
方法從 GitHub 儲存庫封裝查詢和分析最新待解決問題所需的程式碼。
要註冊型別化客戶端,可在 Startup.ConfigureServices
中使用通用的 xref:Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions.AddHttpClient* 擴充套件方法,指定型別化客戶端類:
services.AddHttpClient<GitHubService>();
使用 DI 將型別客戶端註冊為暫時客戶端。 可以直接插入或使用型別化客戶端:
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>();
}
}
}
根據你的喜好,可以在 Startup.ConfigureServices
中註冊時指定型別化客戶端的配置,而不是在型別化客戶端的建構函式中指定:
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
完全封裝在型別化客戶端中。 不是將它公開為屬性,而是可以提供公共方法,用於在內部呼叫 HttpClient
。
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
儲存未私有欄位。 進行外部呼叫的所有訪問都經由 GetRepos
方法。
生成的客戶端
IHttpClientFactory
可結合其他第三方庫(例如 Refit)使用。 Refit
是.NET 的 REST 庫。 它將 REST API
轉換為實時介面。 RestService
動態生成該介面的實現,使用 HttpClient
進行外部 HTTP 呼叫。
定義了介面和答覆來代表外部 API
及其響應:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
可以新增型別化客戶端,使用 Refit
生成實現:
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();
}
可以在必要時使用定義的介面,以及由 DI
和 Refit
提供的實現:
[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
已經具有委託處理程式的概念,這些委託處理程式可以連結在一起,處理出站 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("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);
}
}
上述程式碼定義了基本處理程式。 它檢查請求中是否包含 X-API-KEY
頭。 如果標頭缺失,它可以避免 HTTP 呼叫,並返回合適的響應。
在註冊期間可將一個或多個標頭新增到 HttpClient
的配置。 此任務通過 IHttpClientBuilder
上的擴充套件方法完成。
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>();
在上述程式碼中通過 DI 註冊了 ValidateHeaderHandler
。 IHttpClientFactory
為每個處理程式建立單獨的 DI 作用域。 處理程式可依賴於任何作用域的服務。 處理程式依賴的服務會在處置處理程式時得到處置。
註冊後可以呼叫 AddHttpMessageHandler
,傳入標頭的型別。
可以按處理程式應該執行的順序註冊多個處理程式。 每個處理程式都會覆蓋下一個處理程式,直到最終 HttpClientHandler
執行請求:
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>();
使用以下方法之一將每個請求狀態與訊息處理程式共享:
- 使用
HttpRequestMessage.Properties
將資料傳遞到處理程式。 - 使用
IHttpContextAccessor
訪問當前請求。 - 建立自定義
AsyncLocal
儲存物件以傳遞資料。
使用基於 Polly
的處理程式
IHttpClientFactory
與一個名為 Polly 的熱門第三方庫整合。 Polly
是適用於 .NET 的全面恢復和臨時故障處理庫。 開發人員通過它可以表達策略,例如以流暢且執行緒安全的方式處理重試、斷路器、超時、Bulkhead
隔離和回退。
提供了擴充套件方法,以實現將 Polly
策略用於配置的 HttpClient
例項。 Microsoft.Extensions.Http.Polly NuGet 包中提供 Polly
擴充套件。 Microsoft.AspNetCore.App 元包
中不包括此包。 若要使用擴充套件,專案中應該包括顯式 <PackageReference />
。
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.1.1" />
</ItemGroup>
</Project>
還原此包後,可以使用擴充套件方法來支援將基於 Polly
的處理程式新增至客戶端。
處理臨時故障
大多數常見錯誤在暫時執行外部 HTTP 呼叫時發生。 包含了一種簡便的擴充套件方法,該方法名為 AddTransientHttpErrorPolicy
,允許定義策略來處理臨時故障。 使用這種擴充套件方法配置的策略可以處理 HttpRequestException
、HTTP 5xx 響應以及 HTTP 408 響應。
AddTransientHttpErrorPolicy
擴充套件可在 Startup.ConfigureServices
內使用。 該擴充套件可以提供 PolicyBuilder
物件的訪問許可權,該物件配置為處理表示可能的臨時故障的錯誤:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
上述程式碼中定義了 WaitAndRetryAsync
策略。 請求失敗後最多可以重試三次,每次嘗試間隔 600 ms。
動態選擇策略
存在其他擴充套件方法,可以用於新增基於 Polly 的處理程式。 這類擴充套件的其中一個是 AddPolicyHandler
,它具備多個過載。 一個過載允許在定義要應用的策略時檢查該請求:
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);
在上述程式碼中,如果出站請求為 GET
,則應用 10 秒超時。 其他所有 HTTP 方法應用 30 秒超時。
新增多個 Polly
處理程式
巢狀 Polly
策略以增強功能是很常見的:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
在上述示例中,新增兩個處理程式。 第一個使用 AddTransientHttpErrorPolicy
擴充套件新增重試策略。 若請求失敗,最多可重試三次。 第二個呼叫 AddTransientHttpErrorPolicy
新增斷路器策略。 如果嘗試連續失敗了五次,則會阻止後續外部請求 30 秒。 斷路器策略處於監控狀態。 通過此客戶端進行的所有呼叫都共享同樣的線路狀態。
從 Polly
登錄檔新增策略
管理常用策略的一種方法是一次性定義它們並使用 PolicyRegistry
註冊它們。 提供了一種擴充套件方法,可以使用登錄檔中的策略新增處理程式:
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
在上面的程式碼中,兩個策略在 PolicyRegistry
新增到 ServiceCollection
中時進行註冊。 若要使用登錄檔中的策略,請使用 AddPolicyHandlerFromRegistry
方法,同時傳遞要應用的策略的名稱。
要進一步瞭解 IHttpClientFactory
和 Polly 整合,請參考 Polly Wiki。
HttpClient
和生存期管理
每次對 IHttpClientFactory
呼叫 CreateClient
都會返回一個新 HttpClient
例項。 每個命名的客戶端都具有一個 HttpMessageHandler
。 工廠管理 HttpMessageHandler
例項的生存期。
IHttpClientFactory
將工廠建立的 HttpMessageHandler
例項彙集到池中,以減少資源消耗。 新建 HttpClient
例項時,可能會重用池中的 HttpMessageHandler
例項(如果生存期尚未到期的話)。
由於每個處理程式通常管理自己的基礎 HTTP
連線,因此需要池化處理程式。 建立超出必要數量的處理程式可能會導致連線延遲。 部分處理程式還保持連線無期限地開啟,這樣可以防止處理程式對 DNS 更改作出反應。
處理程式的預設生存期為兩分鐘。 可在每個命名客戶端上重寫預設值。 要重寫該值,請在建立客戶端時在返回的 IHttpClientBuilder
上呼叫 SetHandlerLifetime
:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
無需處置客戶端。 處置既取消傳出請求,又保證在呼叫 Dispose
後無法使用給定的 HttpClient
例項。 IHttpClientFactory
跟蹤和處置 HttpClient
例項使用的資源。 HttpClient
例項通常可視為無需處置的 .NET 物件。
保持各個 HttpClient
例項長時間處於活動狀態是在 IHttpClientFactory
推出前使用的常見模式。 遷移到 IHttpClientFactory
後,就無需再使用此模式。
日誌記錄
通過 IHttpClientFactory
建立的客戶端記錄所有請求的日誌訊息。 在日誌記錄配置中啟用合適的資訊級別可以檢視預設日誌訊息。 僅在跟蹤級別包含附加日誌記錄(例如請求標頭的日誌記錄)。
用於每個客戶端的日誌類別包含客戶端名稱。 例如,名為“MyNamedClient”的客戶端使用 System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
類別來記錄訊息。 字尾為 LogicalHandler
的訊息在請求處理程式管道外部發生。 在請求時,在管道中的任何其他處理程式處理請求之前記錄訊息。 在響應時,在任何其他管道處理程式接收響應之後記錄訊息。
日誌記錄還在請求處理程式管道內部發生。 在“MyNamedClient”示例中,這些訊息是針對日誌類別 System.Net.Http.HttpClient.MyNamedClient.ClientHandler
進行記錄。 在請求時,在所有其他處理程式執行後,以及剛好在通過網路發出請求之前記錄訊息。 在響應時,此日誌記錄包含響應在通過處理程式管道被傳遞回去之前的狀態。
在管道內外啟用日誌記錄,可以檢查其他管道處理程式做出的更改。 例如,其中可能包含對請求標頭的更改,或者對響應狀態程式碼的更改。
通過在日誌類別中包含客戶端名稱,可以在必要時對特定的命名客戶端篩選日誌。
配置 HttpMessageHandler
控制客戶端使用的內部 HttpMessageHandler
的配置是有必要的。
在新增命名客戶端或型別化客戶端時,會返回 IHttpClientBuilder
。 ConfigurePrimaryHttpMessageHandler
擴充套件方法可以用於定義委託。 委託用於建立和配置客戶端使用的主要 HttpMessageHandler
:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
相關文章
- ASP.NET Core基礎知識(四)【路由】ASP.NET路由
- ASP.NET Core基礎知識(一)【概述】ASP.NET
- 理解ASP.NET Core - 傳送Http請求(HttpClient)ASP.NETHTTPclient
- JavaSE基礎知識分享(十四)Java
- ASP.NET Core 2.2 基礎知識(七)【選項】ASP.NET
- 第十四章 HTTP請求HTTP
- ASP.NET Core 2.2 基礎知識(十)【中介軟體】ASP.NET
- ASP.NET Core 2.2 基礎知識(十三)【伺服器】ASP.NET伺服器
- ASP.NET Core基礎知識(二)【應用啟動】ASP.NET
- 圖解http,http基礎知識圖解HTTP
- ASP.NET Core - 實現Http自定義請求頭策略ASP.NETHTTP
- ASP.NET Core 2.2 基礎知識(六)【Configuration】ASP.NET
- ASP.NET Core 2.2 基礎知識(八)【日誌記錄】ASP.NET
- ASP.NET Core 2.2 基礎知識(九)【處理錯誤】ASP.NET
- ASP.NET Core基礎知識(十一)【Host之Web 主機】ASP.NETWeb
- ASP.NET Core 基礎知識(十二)【Host之通用主機】ASP.NET
- ASP.NET Core基礎知識(五)【環境(開發、分階段、生產)】ASP.NET
- ASP.NET Core中介軟體計算Http請求時間ASP.NETHTTP
- ASP.NET Core擴充套件庫之Http請求模擬ASP.NET套件HTTP
- 在ASP.NET Core中用HttpClient(三)——傳送HTTP PATCH請求ASP.NETHTTPclient
- React技巧之發出http請求ReactHTTP
- 第十四章 http請求(bjsuo)HTTPJS
- 在ASP.NET Core中用HttpClient(五)——通過CancellationToken取消HTTP請求ASP.NETHTTPclient
- ASP.NET Core基礎知識(三)【依賴關係注入(服務)】ASP.NET
- ASP.NET Core管道深度剖析(1):採用管道處理HTTP請求ASP.NETHTTP
- 開發基礎知識
- ASP.NET程式安全的基礎知識ASP.NET
- 你應該知道的HTTP基礎知識HTTP
- JavaScript中發出HTTP請求最常用的方法JavaScriptHTTP
- Java併發--基礎知識Java
- 蘋果開發基礎知識蘋果
- 菜鳥請教rbac的基礎知識
- ASP.NET Core獲取請求完整的UrlASP.NET
- ASP.NET Core - 請求管道與中介軟體ASP.NET
- angular學習筆記(二十四)-$http(2)-設定http請求頭Angular筆記HTTP
- 小范筆記:ASP.NET Core API 基礎知識與Axios前端提交資料筆記ASP.NETAPIiOS前端
- 如何使用axios發出高大上的HTTP請求iOSHTTP
- http請求HTTP