使用 HttpClientFactory 實作復原 HTTP 要求Use HttpClientFactory to implement resilient HTTP requests

HttpClientFactory 是意向明確的處理站,自 .NET Core 2.1 開始提供,可用來建立要在您應用程式中使用的 HttpClient 執行個體。HttpClientFactory is an opinionated factory, available since .NET Core 2.1, for creating HttpClient instances to be used in your applications.

.NET Core 中原始 HttpClient 類別的問題Issues with the original HttpClient class available in .NET Core

已知的原始 HttpClient 類別使用方式簡單,但在某些情況下,許多開發人員都沒有正確地使用它。The original and well-known HttpClient class can be easily used, but in some cases, it isn't being properly used by many developers.

第一個問題是,這個類別是可處置項目,將它與 using 陳述式搭配使用不是最好的選擇,因為即使您處置 HttpClient 物件,底層通訊端也不會立即釋出,而且可能會導致所謂「通訊端耗盡」的嚴重問題。As a first issue, while this class is disposable, using it with the using statement is not the best choice because even when you dispose HttpClient object, the underlying socket is not immediately released and can cause a serious issue named ‘sockets exhaustion’. 如需有關此問題的詳細資訊,請參閱您使用 HttpClient 的方法錯誤而導致軟體不穩定 部落格文章 (英文)。For more information about this issue, see You're using HttpClient wrong and it's destabilizing your software blog post.

因此,您應該將 HttpClient 具現化一次,然後在整個應用程式生命週期中重複使用。Therefore, HttpClient is intended to be instantiated once and reused throughout the life of an application. 為每個要求具現化 HttpClient 類別將會在負載過重時耗盡可用的通訊端數目。Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. 該問題會導致 SocketException 錯誤。That issue will result in SocketException errors. 解決該問題的可能方法為建立 HttpClient 物件做為單一物件或靜態物件,如 Microsoft 關於 HttpClient 用法的文章中所述。Possible approaches to solve that problem are based on the creation of the HttpClient object as singleton or static, as explained in this Microsoft article on HttpClient usage.

但是將 HttpClient 做為單一物件或靜態物件時會出現第二個問題。But there’s a second issue with HttpClient that you can have when you use it as singleton or static object. 在此案例中,單一或靜態 HttpClient 不會理會 DNS 變更,如 .NET Core GitHub 存放庫提及的問題中所述。In this case, a singleton or static HttpClient doesn't respect DNS changes, as explained in this issue at the .NET Core GitHub repo.

為解決所述的那些問題並更輕鬆地管理 HttpClient 執行個體,.NET Core 2.1 引進了一個新的 HttpClientFactory,它也可與 Polly 整合來實作復原 HTTP 呼叫。To address those mentioned issues and make the management of HttpClient instances easier, .NET Core 2.1 introduced a new HttpClientFactory that can also be used to implement resilient HTTP calls by integrating Polly with it.

什麼是 HttpClientFactoryWhat is HttpClientFactory

HttpClientFactory 的設計宗旨是:HttpClientFactory is designed to:

  • 提供一個集中位置以便命名及設定邏輯 HttpClients。Provide a central location for naming and configuring logical HttpClients. 例如,您可以設定一個已預先設定為存取特定微服務的用戶端 (服務代理程式)。For example, you may configure a client (Service Agent) that's pre-configured to access a specific microservice.
  • 透過委派 HttpClient 中的處理常式和實作 Polly 型中介軟體以利用 Polly 原則用於復原,來編纂連出中介軟體的概念。Codify the concept of outgoing middleware via delegating handlers in HttpClient and implementing Polly-based middleware to take advantage of Polly’s policies for resiliency.
  • HttpClient 已經有委派可針對傳出 HTTP 要求連結在一起之處理常式的概念。HttpClient already has the concept of delegating handlers that could be linked together for outgoing HTTP requests. 您將 http 用戶端註冊到處理站,而且您可以使用允許 Polly 原則用於重試、斷路器等等的 Polly 處理常式。You register http clients into the factory plus you can use a Polly handler that allows Polly policies to be used for Retry, CircuitBreakers, etc.
  • 管理 HttpClientMessageHandlers 存留期,以避免您自己管理 HttpClient 存留期時可能發生的上述問題。Manage the lifetime of HttpClientMessageHandlers to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

使用 HttpClientFactory 的多種方式Multiple ways to use HttpClientFactory

有多種方式可在您的應用程式中使用 HttpClientFactoryThere are several ways that you can use HttpClientFactory in your application:

  • 直接使用 HttpClientFactoryUse HttpClientFactory Directly
  • 使用具名用戶端Use Named Clients
  • 使用具型別用戶端Use Typed Clients
  • 使用產生的用戶端Use Generated Clients

為了簡潔起見,此指導方針展示如何以結構化程度最高的方式使用 HttpClientFactory,也就是使用具型別用戶端 (服務代理程式模式),但是我們已記錄所有選項,而且目前已詳列在這篇涵蓋 HttpClientFactory 用法的文章中。For the sake of brevity, this guidance shows the most structured way to use HttpClientFactory that's to use Typed Clients (Service Agent pattern), but all options are documented and are currently listed in this article covering HttpClientFactory usage.

如何搭配 HttpClientFactory 使用具型別用戶端How to use Typed Clients with HttpClientFactory

那麼,什麼是「具型別用戶端」?So, what's a "Typed Client"? 它只是 DefaultHttpClientFactory 插入時所設定的一個 HttpClientIt's just an HttpClient that's configured upon injection by the DefaultHttpClientFactory.

下圖顯示具型別用戶端如何搭配 HttpClientFactory 使用:The following diagram shows how Typed Clients are used with HttpClientFactory:

ClientService (控制器或用戶端程式碼所使用) 會使用由已註冊的 IHttpClientFactory 所建立的 HttpClient。

圖 8-4Figure 8-4. 搭配具型別用戶端類別使用 HttpClientFactory。Using HttpClientFactory with Typed Client classes.

首先,在應用程式中設定 HttpClientFactory,加入對 Microsoft.Extensions.Http 的參考,其中包括 IServiceCollectionAddHttpClient() 擴充方法。First, setup HttpClientFactory in your application, adding a reference to the Microsoft.Extensions.Http package that includes the AddHttpClient() extension method for IServiceCollection. 這個擴充方法註冊將用來做為介面 IHttpClientFactory 之單一物件的 DefaultHttpClientFactoryThis extension method registers the DefaultHttpClientFactory to be used as a singleton for the interface IHttpClientFactory. 它為 HttpMessageHandlerBuilder 定義暫時性設定。It defines a transient configuration for the HttpMessageHandlerBuilder. 此訊息處理常式 (HttpMessageHandler 物件) 取自集區,供處理站傳回的 HttpClient 使用。This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.

在下一個程式碼中,您可以看到如何使用 AddHttpClient() 來註冊需要使用 HttpClient 的具型別用戶端 (服務代理程式)。In the next code, you can see how AddHttpClient() can be used to register Typed Clients (Service Agents) that need to use HttpClient.

// Startup.cs
//Add http client services at ConfigureServices(IServiceCollection services) 
services.AddHttpClient<ICatalogService, CatalogService>();
services.AddHttpClient<IBasketService, BasketService>();
services.AddHttpClient<IOrderingService, OrderingService>();

如先前的程式碼所示,註冊用戶端服務,讓 DefaultClientFactory 建立特別針對每個服務設定的 HttpClient,我們將在下一個段落中說明。Registering the client services as shown in the previous code, makes the DefaultClientFactory create an HttpClient configured specifically for each service, as we'll explain in the next paragraph.

只要透過 AddHttpClient() 註冊您的用戶端服務類別,將插入您類別中的 HttpClient 物件將使用註冊時所提供的設定和原則。Just by registering your client service class with AddHttpClient(), the HttpClient object that will be injected into your class will use the configuration and policies provided upon registration. 在以下各節中,您將會看到那些原則,如 Polly 的重試或斷路器。In the next sections, you'll see those policies like Polly’s retries or circuit-breakers.

HttpClient 存留期HttpClient lifetimes

每次您從 IHttpClientFactory 取得 HttpClient 物件時,都會傳回一個新的執行個體。Each time you get an HttpClient object from the IHttpClientFactory, a new instance is returned. 但只要 HttpMessageHandler 的存留期間尚未過期,每個 HttpClient 就都會使用 HttpMessageHandler,它會由 IHttpClientFactory 組成集區並重複使用以減少資源耗用量。But each HttpClient uses an HttpMessageHandler that's pooled and reused by the IHttpClientFactory to reduce resource consumption, as long as the HttpMessageHandler's lifetime hasn't expired.

處理常式的集合是需要的做法,因為每個處理常式通常會管理自己的基礎 HTTP 連線;建立比所需數目更多的處理常式可能會導致連線延遲。Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections; creating more handlers than necessary can result in connection delays. 有些處理常式也會保持連線無限期地開啟,這可能導致處理常式無法對 DNS 變更回應。Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

集區中的 HttpMessageHandler 物件有存留期,也就是集區中該 HttpMessageHandler 執行個體可重複使用的時間長度。The HttpMessageHandler objects in the pool have a lifetime that's the length of time that an HttpMessageHandler instance in the pool can be reused. 預設值為 2 分鐘,但是可針對各個具型別用戶端分別覆寫。The default value is two minutes, but it can be overridden per Typed Client. 若要覆寫它,請在建立用戶端時所傳回的 IHttpClientBuilder 上呼叫 SetHandlerLifetime(),如下列程式碼所示:To override it, call SetHandlerLifetime() on the IHttpClientBuilder that's returned when creating the client, as shown in the following code:

//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client 
services.AddHttpClient<ICatalogService, CatalogService>()
                 .SetHandlerLifetime(TimeSpan.FromMinutes(5));  

每個具型別用戶端都可以設定自己的處理常式存留期值。Each Typed Client can have its own configured handler lifetime value. 將存留期設定成 InfiniteTimeSpan 以停用處理常式到期時間。Set the lifetime to InfiniteTimeSpan to disable handler expiry.

實作使用已插入和已設定之 HttpClient 的具型別用戶端類別Implement your Typed Client classes that use the injected and configured HttpClient

在上一個步驟中,您必須先定義具型別用戶端類別,例如範例程式碼中的類別,像是 'BasketService'、'CatalogService'、'OrderingService' 等等,具型別用戶端是接受 HttpClient 物件 (透過建構函式插入) 並使用該物件來呼叫某些遠端 HTTP 服務的類別。As a previous step, you need to have your Typed Client classes defined, such as the classes in the sample code, like ‘BasketService’, ‘CatalogService’, ‘OrderingService’, etc. – A Typed Client is a class that accepts an HttpClient object (injected through its constructor) and uses it to call some remote HTTP service. 例如:For example:

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 之外,它可以接受其建構函式中任何已註冊的服務。The Typed Client (CatalogService in the example) is activated by DI (Dependency Injection), meaning that it can accept any registered service in its constructor, in addition to HttpClient.

具型別用戶端實際上是暫時性物件,也就是說只要需要就會建立新的執行個體,而且每次建構它時都會收到一個新的 HttpClient 執行個體。A Typed Client is, effectively, a transient object, meaning that a new instance is created each time one is needed and it will receive a new HttpClient instance each time it's constructed. 不過,集區中的 HttpMessageHandler 物件是可讓多個 Http 要求重複使用的物件。However, the HttpMessageHandler objects in the pool are the objects that are reused by multiple Http requests.

使用具型別用戶端類別Use your Typed Client classes

最後,實作類型類別並使用 AddHttpClient() 註冊之後,即可在 DI 插入的服務中隨意使用它,例如在任何 Razor 頁面程式碼或任何 MVC Web 應用程式控制器,就像來自 eShopOnContainers 的下列程式碼一樣。Finally, once you have your type classes implemented and have them registered with AddHttpClient(), you can use it anywhere you can have services injected by DI, for example in any Razor page code or any controller of an MVC web app, like in the following code from 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 要求,在運作時會考慮使用帳戶復原原則 (例如利用指數輪詢和斷路器來重試),或是任何其他自訂委派處理常式來實作其他安全性功能 (例如使用驗證權杖) 或任何其他自訂功能。Up to this point, the code shown is just performing regular Http requests, but the ‘magic’ comes in the following sections where, just by adding policies and delegating handlers to your registered Typed Clients, all the HTTP requests to be done by HttpClient will behave taking into account resilient policies such as retries with exponential backoff, circuit breakers, or any other custom delegating handler to implement additional security features, like using auth tokens, or any other custom feature.

其他資源Additional resources