使用 IHttpClientFactory 實作有彈性的 HTTP 要求
IHttpClientFactory 是由 DefaultHttpClientFactory
所實作的合約,自 .NET Core 2.1 起提供的具見解的工廠,用於建立以供您的應用程式使用的 HttpClient 實例。
.NET 中可用的原始 HttpClient 類別問題
原始且知名的 HttpClient 類別很容易使用,但在某些情況下,許多開發人員並未正確使用。
雖然這個類別實作了 IDisposable
,但在 using
語句中宣告和實例化它並不建議,因為在 HttpClient
物件被處置後,基礎套接字不會立即釋放,這可能導致 套接字耗盡 的問題。 如需此問題的詳細資訊,請參閱部落格文章 您使用 HttpClient 錯誤,且軟體不穩定。
因此,HttpClient
旨在具現化一次,並在應用程式的整個生命週期中重複使用。 在大量負載的情況下,每次請求都實例化一個 HttpClient
類別將會耗盡可用的套接字數目。 此問題會導致 SocketException
錯誤。 解決該問題的可能方法是,如這篇 Microsoft 文章關於中的 HttpClient 用法所述,將 HttpClient
對象創建為單例或靜態。 這對於每天執行幾次的短暫使用的控制台應用程式或類似用途來說,是一個不錯的解決方案。
開發人員常遇到的一個問題是,在長時間運行的程序中使用 HttpClient
的共享實例。 在 HttpClient 初始化為單一物件或靜態物件的情況下,它無法處理 DNS 變更,正如在 dotnet/runtime GitHub 存放庫中所述的 問題。
不過,問題並不真正具有 HttpClient
,而是使用 HttpClient 的預設建構函式,因為它會建立 HttpMessageHandler的新具體實例,也就是具有 套接字耗盡 和上述 DNS 變更問題。
為了解決上述問題,並讓 HttpClient
實例能夠管理,.NET Core 2.1 引進了兩種方法,其中一種方法是 IHttpClientFactory。 這是一個介面,可用來透過相依性插入 (DI) 在應用程式中設定和建立 HttpClient
實例。 它也提供 Polly 型中間件的延伸模組,以利用 HttpClient 中的委派處理程式。
替代方案是使用已設定的 PooledConnectionLifetime
搭配 SocketsHttpHandler
。 此方法會套用至持久的 static
或單一 HttpClient
實例。 若要深入瞭解不同的策略,請參閱 .NET的 HttpClient 指導方針。
Polly 是暫時性錯誤處理連結庫,可協助開發人員使用一些預先定義的原則,以流暢且安全線程的方式,將復原功能新增至其應用程式。
使用 IHttpClientFactory 的優點
IHttpClientFactory的目前實作,也會實作 IHttpMessageHandlerFactory,提供下列優點:
- 提供命名和設定邏輯
HttpClient
物件的中央位置。 例如,您可以設定預先設定來存取特定微服務的客戶端(服務代理程式)。 - 將傳出中間件的概念標準化,透過委派
HttpClient
中的處理程序,並實作基於 Polly 的中間件,利用 Polly 的策略來增強韌性。 -
HttpClient
已經有委派處理程式的概念,這些處理程式可以連結在一起以供傳出 HTTP 要求使用。 您可以將 HTTP 客戶端註冊到工廠中,並且可以使用 Polly 處理程式來實現 Polly 原則,例如重試機制、斷路器等。 - 管理 HttpMessageHandler 的生命周期,以避免自行管理
HttpClient
生命周期時可能發生的問題。
提示
DI 插入的 HttpClient
實例可以安全地處置,因為相關聯的 HttpMessageHandler
是由處理站所管理。 插入 HttpClient
實體會從 DI 的觀點 暫時性,而 HttpMessageHandler
實體可視為 範圍。
HttpMessageHandler
實例有自己的 DI 範圍,與應用程式範圍分開(例如,ASP.NET 連入要求範圍)。 如需詳細資訊,請參閱在 .NET 中使用 HttpClientFactory 。
注意
IHttpClientFactory
(DefaultHttpClientFactory
) 的實作與 Microsoft.Extensions.DependencyInjection
NuGet 套件中的 DI 實作緊密系結。 如果您需要在沒有 DI 或與其他 DI 實作一起使用 HttpClient
的情況下,建議考慮使用 static
或設置有 PooledConnectionLifetime
的單一 HttpClient
。 如需詳細資訊,請參閱 .NET的 HttpClient 指導方針。
使用 IHttpClientFactory 的多種方式
有數種方式可讓您在應用程式中使用 IHttpClientFactory
:
- 基本用法
- 使用具名用戶端
- 使用具類型的用戶端
- 使用產生的用戶端
為了簡潔起見,本指南會顯示使用 IHttpClientFactory
的最結構化方式,也就是使用具類型的用戶端(服務代理程式模式)。 不過,所有選項都有記載,且目前列在這篇 文章中,討論 IHttpClientFactory
用法。
注意
如果您的應用程式需要 Cookie,最好避免在應用程式中使用 IHttpClientFactory。 如需管理用戶端的替代方式,請參閱使用 HTTP 用戶端 的指導方針。
如何搭配 IHttpClientFactory 使用具類型的用戶端
那麼,什麼是「類型化的用戶端」? 這隻是針對某些特定用途預先設定的 HttpClient
。 此設定可以包含特定值,例如基底伺服器、HTTP 標頭或逾時。
以下圖表顯示如何搭配 IHttpClientFactory
使用具型別用戶端:
圖 8-4。 使用 IHttpClientFactory
來搭配具型別的客戶端類別。
在上圖中,ClientService
(由控制器或客戶端程式碼使用)會使用註冊的 IHttpClientFactory
所建立的 HttpClient
。 此工廠會將池中的 HttpMessageHandler
指派給 HttpClient
。 註冊 IHttpClientFactory
至 DI 容器時,可以使用擴充方法 AddHttpClient並配置 Polly 的策略來設定 HttpClient
。
若要設定上述結構,請安裝包含 IServiceCollectionAddHttpClient 擴充方法的 Microsoft.Extensions.Http
NuGet 套件,以在應用程式中新增 IHttpClientFactory。 這個擴充方法會註冊內部 DefaultHttpClientFactory
類別,做為介面 IHttpClientFactory
的單一類別。 它會定義 HttpMessageHandlerBuilder的暫時性組態。 從集區擷取的這個訊息處理程式(HttpMessageHandler 物件),是由從工廠傳回的 HttpClient
使用。
在下一個代碼段中,您可以看到如何使用 AddHttpClient()
來註冊需要使用 HttpClient
的具型別客戶端(服務代理程式)。
// Program.cs
//Add http client services at ConfigureServices(IServiceCollection services)
builder.Services.AddHttpClient<ICatalogService, CatalogService>();
builder.Services.AddHttpClient<IBasketService, BasketService>();
builder.Services.AddHttpClient<IOrderingService, OrderingService>();
如上一個代碼段所示註冊用戶端服務,讓 DefaultClientFactory
為每個服務建立標準 HttpClient
。 型別化的用戶端會註冊為暫時性並加入 DI 容器。 在上述程式代碼中,AddHttpClient()
註冊 CatalogService、BasketService、OrderingService 為暫時性服務,因此不需要額外的註冊,即可直接插入和取用它們。
您也可以在註冊中新增實例特定的組態,例如,設定基位址,並新增一些復原原則,如下所示:
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
在下一個範例中,您可以看到上述其中一個原則的設定:
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
您可以在下一篇文章 中找到更多有關使用 Polly 的詳細資訊。
HttpClient 存留期
每次從 IHttpClientFactory
取得 HttpClient
物件時,都會傳回新的實例。 但是,只要 HttpMessageHandler
的存留期尚未過期,每個 HttpClient
都會使用由 IHttpClientFactory
集中和重複使用的 HttpMessageHandler
,以減少資源消耗。
需要共用處理程式,因為每個處理程式通常會管理自己的基礎 HTTP 連線;建立比必要更多的處理程式可能會導致連線延遲。 某些處理程式也會無限期地保持連線開啟,這可以防止處理程序回應 DNS 變更。
集區中的 HttpMessageHandler
物件有存留期,這是可以重複使用集區中 HttpMessageHandler
實例的時間長度。 默認值為兩分鐘,但可以覆寫每個類型化客戶端。 若要覆寫它,請在建立用戶端時傳回的 IHttpClientBuilder 上呼叫 SetHandlerLifetime()
,如下列程式代碼所示:
//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client
builder.Services.AddHttpClient<ICatalogService, CatalogService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
每個具類型的用戶端都可以有自己的已設定處理程式存留期值。 將生命週期設定為 InfiniteTimeSpan
以停用處理程序的到期。
實作使用已注入並設定的 HttpClient 的型別化客戶端類別
在上一個步驟中,您必須先定義具類型的客戶端類別,例如範例程式代碼中的類別,如 'BasketService'、'CatalogService'、'OrderingService' 等等。具類型的客戶端是一種類別,透過其構造函數注入 HttpClient
對象,並使用該對象來調用某些遠程 HTTP 服務。 例如:
public class CatalogService : ICatalogService
{
private readonly HttpClient _httpClient;
private readonly string _remoteServiceBaseUrl;
public CatalogService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Catalog> GetCatalogItems(int page, int take,
int? brand, int? type)
{
var uri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl,
page, take, brand, type);
var responseString = await _httpClient.GetStringAsync(uri);
var catalog = JsonConvert.DeserializeObject<Catalog>(responseString);
return catalog;
}
}
具類型用戶端(範例中的CatalogService
)是由 DI(相依性注入)啟動,這表示除了 HttpClient
之外,它還可以在其建構函式中接受任何已註冊的服務。
類型化客戶端本質上是一個短暫物件,這意味著每次需要時都會創建新的實例。 每次建構這個程式時,都會收到一個新的 HttpClient
實例。 不過,資源池中的 HttpMessageHandler
物件是由多個 HttpClient
實例重複使用的物件。
使用類型化客戶端類別
最後,當您實作型別化的類別之後,就可以使用 AddHttpClient()
註冊並配置這些類別。 之後,您可以在通過 DI(依賴注入)注入服務的位置使用它們,例如在 Razor 頁面的代碼或 MVC Web 應用程式控制器中,如下方來自 eShopOnContainers 的程式碼所示:
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
public class CatalogController : Controller
{
private ICatalogService _catalogSvc;
public CatalogController(ICatalogService catalogSvc) =>
_catalogSvc = catalogSvc;
public async Task<IActionResult> Index(int? BrandFilterApplied,
int? TypesFilterApplied,
int? page,
[FromQuery]string errorMsg)
{
var itemsPage = 10;
var catalog = await _catalogSvc.GetCatalogItems(page ?? 0,
itemsPage,
BrandFilterApplied,
TypesFilterApplied);
//… Additional code
}
}
}
目前為止,上述代碼段只會顯示執行一般 HTTP 要求的範例。 但「魔術」存在於以下各節,說明 HttpClient
發出的所有 HTTP 請求如何具備穩健性策略,例如使用指數回退的重試、熔斷機制、使用認證令牌的安全功能,甚至其他任何自定義功能。 這些工作只需將策略配置到已登錄的具類型客戶端並指派處理程序即可完成。
其他資源
.NEThttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines 的 HttpClient 指導方針
在 .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory 中使用 HttpClientFactory
在 ASP.NET Core 中使用 HttpClientFactory https://learn.microsoft.com/aspnet/core/fundamentals/http-requests
GitHub 存放庫中的
dotnet/runtime
HttpClientFactory 原始碼https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (.NET 復原和暫時性錯誤處理程式庫)https://thepollyproject.azurewebsites.net/