使用 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 變更,如 dotnet/corefx GitHub 存放庫中的問題中所述。In this case, a singleton or static HttpClient doesn't respect DNS changes, as explained in this issue at the dotnet/corefx GitHub repository.

為解決所述的那些問題並更輕鬆地管理 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.

Polly是暫時性錯誤處理程式庫,可協助開發人員以流暢且安全線程的方式使用一些預先定義的原則,來為應用程式新增復原功能。Polly is transient-fault-handling library that helps developers add resiliency to their applications, by using some pre-defined policies in a fluent and thread-safe manner.

什麼是 HttpClientFactoryWhat is HttpClientFactory

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

  • 提供用來命名和設定邏輯 HttpClient 物件的中央位置。Provide a central location for naming and configuring logical HttpClient objects. 例如,您可以設定一個已預先設定為存取特定微服務的用戶端 (服務代理程式)。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 and you can use a Polly handler to use Polly policies for Retry, CircuitBreakers, and so on.
  • 管理 HttpClientMessageHandlers 的存留期,以避免所提及的問題/在自行管理 HttpClient 存留期時可能發生的問題。Manage the lifetime of HttpClientMessageHandlers to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

注意

HttpClientFactory 緊密系結至 Microsoft.Extensions.DependencyInjection NuGet 封裝中的相依性插入(DI)實作為關聯性。HttpClientFactory is tightly tied to the dependency injection (DI) implementation in the Microsoft.Extensions.DependencyInjection NuGet package. 如需使用其他相依性插入容器的詳細資訊,請參閱此GitHub 討論For more information about using other dependency injection containers, see this GitHub discussion.

使用 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 的最結構化方式,也就是使用具型別用戶端(服務代理程式模式)。For the sake of brevity, this guidance shows the most structured way to use HttpClientFactory, which is to use Typed Clients (Service Agent pattern). 不過,所有選項都已記載,而且目前列于本文中,涵蓋 HttpClientFactory 的使用方式。However, 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:

顯示型別用戶端如何搭配 HttpClientFactory 使用的圖表。

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

在上圖中,ClientService (由控制器或用戶端程式代碼使用)會使用已註冊 IHttpClientFactory所建立的 HttpClientIn the above image, a ClientService (used by a controller or client code) uses an HttpClient created by the registered IHttpClientFactory. 此處理站會從所管理的集區中指派 HttpMessageHandlerHttpClientThis factory assigns the HttpClient an HttpMessageHandler from a pool it manages. 在使用擴充方法 AddHttpClient的 DI 容器中註冊 IHttpClientFactory 時,可以使用 Polly 的原則來設定 HttpClientThe HttpClient can be configured with Polly's policies when registering the IHttpClientFactory in the DI container with the extension method AddHttpClient.

若要設定上述結構,請安裝包含 IServiceCollection``AddHttpClient() 擴充方法的 Microsoft.Extensions.Http NuGet 封裝,以在應用程式中新增 HttpClientFactoryTo configure the above structure, add HttpClientFactory in your application by installing the Microsoft.Extensions.Http NuGet 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 為每個服務建立標準 HttpClientRegistering the client services as shown in the previous code, makes the DefaultClientFactory create a standard HttpClient for each service.

您也可以在註冊中新增實例特定設定,例如設定基底位址,然後新增一些復原原則,如下列程式碼所示:You could also add instance-specific configuration in the registration to, for example, configure the base address, and add some resiliency policies, as shown in the following code:

services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
    client.BaseAddress = new Uri(Configuration["BaseUrl"]);
})
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

就此範例而言,您可以在下一個程式碼中看到上述其中一個原則:Just for the example sake, you can see one of the above policies in the next code:

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 的詳細資料。You can find more details about using Polly in the Next article.

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 插入服務的地方使用它們。Finally, once you have your typed classes implemented and have them registered with AddHttpClient(), you can use them wherever you can have services injected by DI. 例如,在 Razor 頁面程式碼或 MVC web 應用程式的控制器中,如下列 eShopOnContainers 程式碼所示:For example, in a Razor page code or 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