Usar HttpClientFactory implementar solicitações HTTP resilientesUse HttpClientFactory to implement resilient HTTP requests

HttpClientFactory é um alocador "teimoso", disponível desde o .NET Core 2.1, para a criação de instâncias do HttpClient a serem usadas nos aplicativos.HttpClientFactory is an opinionated factory, available since .NET Core 2.1, for creating HttpClient instances to be used in your applications.

Problemas com a classe HttpClient original disponível no .NET CoreIssues with the original HttpClient class available in .NET Core

A classe de HttpClient original e conhecida pode ser facilmente usada, mas em alguns casos, ela não está sendo usada corretamente por muitos desenvolvedores.The original and well-known HttpClient class can be easily used, but in some cases, it isn't being properly used by many developers.

Como um primeiro problema, embora essa classe seja descartável, usá-la com a instrução using não é a melhor opção porque, mesmo quando você descarta o objeto HttpClient, o soquete subjacente não é liberado imediatamente e pode causar um problema sério chamado 'esgotamento de soquetes'.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’. Para obter mais informações sobre esse problema, confira a postagem no blog You're using HttpClient wrong and it is destabilizing your software (Você está usando o HttpClient incorretamente e ele está desestabilizando o software).For more information about this issue, see You're using HttpClient wrong and it's destabilizing your software blog post.

Portanto, HttpClient deve ser instanciado uma única vez e reutilizado durante a vida útil de um aplicativo.Therefore, HttpClient is intended to be instantiated once and reused throughout the life of an application. A criação de uma instância de uma classe HttpClient para cada solicitação esgotará o número de soquetes disponíveis em condições de carga pesada.Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. Esse problema resultará em erros de SocketException.That issue will result in SocketException errors. Abordagens possíveis para resolver o problema baseiam-se na criação do objeto HttpClient como singleton ou estático, conforme é explicado neste artigo da Microsoft sobre o uso do 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.

Mas há um segundo problema com o HttpClient que pode ocorrer quando ele é usado como um objeto singleton ou estático.But there’s a second issue with HttpClient that you can have when you use it as singleton or static object. Nesse caso, um singleton ou HttpClient estático não respeitam as alterações de DNS, conforme explicado neste problema no repositório do GitHub dotnet/corefx.In this case, a singleton or static HttpClient doesn't respect DNS changes, as explained in this issue at the dotnet/corefx GitHub repository.

Para resolver esses problemas mencionados e facilitar o gerenciamento das instâncias do HttpClient, o .NET Core 2.1 introduziu um novo HttpClientFactory que também pode ser usado para implementar chamadas HTTP resilientes pela integração do Polly a ele.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 é uma biblioteca de tratamento de falhas transitórias que ajuda os desenvolvedores a adicionar resiliência aos seus aplicativos, usando algumas políticas predefinidas de forma fluente e thread-safe.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.

O que é o HttpClientFactoryWhat is HttpClientFactory

O HttpClientFactory foi projetado para:HttpClientFactory is designed to:

  • Forneça um local central para nomear e configurar objetos de HttpClient lógicos.Provide a central location for naming and configuring logical HttpClient objects. Por exemplo, você pode configurar um cliente (agente de serviço) pré-configurado para acessar um microsserviço específico.For example, you may configure a client (Service Agent) that's pre-configured to access a specific microservice.
  • Codificar o conceito de middleware de saída por meio da delegação de manipuladores no HttpClient e da implementação de middleware baseado em Polly para aproveitar as políticas da Polly e garantir a resiliência.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.
  • O HttpClient já tem o conceito de delegar manipuladores que podem ser vinculados uns aos outros para solicitações HTTP de saída.HttpClient already has the concept of delegating handlers that could be linked together for outgoing HTTP requests. Você registra clientes HTTP na fábrica e pode usar um manipulador Polly para usar políticas Polly para repetição, CircuitBreakers e assim por diante.You register HTTP clients into the factory and you can use a Polly handler to use Polly policies for Retry, CircuitBreakers, and so on.
  • Gerencie o tempo de vida de HttpClientMessageHandlers para evitar problemas/problemas mencionados que podem ocorrer ao gerenciar HttpClient os tempos de vida.Manage the lifetime of HttpClientMessageHandlers to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

Observação

HttpClientFactory está rigidamente ligado à implementação de injeção de dependência (DI) no pacote NuGet Microsoft.Extensions.DependencyInjection.HttpClientFactory is tightly tied to the dependency injection (DI) implementation in the Microsoft.Extensions.DependencyInjection NuGet package. Para obter mais informações sobre como usar outros contêineres de injeção de dependência, consulte esta discussão do GitHub.For more information about using other dependency injection containers, see this GitHub discussion.

Várias maneiras de usar o HttpClientFactoryMultiple ways to use HttpClientFactory

Há várias maneiras de usar o HttpClientFactory no aplicativo:There are several ways that you can use HttpClientFactory in your application:

  • Usar o HttpClientFactory diretamenteUse HttpClientFactory Directly
  • Usar clientes nomeadosUse Named Clients
  • Usar clientes tipadosUse Typed Clients
  • Usar clientes geradosUse Generated Clients

Para fins de brevidade, essas diretrizes mostram a maneira mais estruturada de usar HttpClientFactory, que é usar clientes digitados (padrão de agente de serviço).For the sake of brevity, this guidance shows the most structured way to use HttpClientFactory, which is to use Typed Clients (Service Agent pattern). No entanto, todas as opções estão documentadas e atualmente estão listadas neste artigo cobrindo o uso do HttpClientFactory.However, all options are documented and are currently listed in this article covering HttpClientFactory usage.

Como usar clientes tipados com HttpClientFactoryHow to use Typed Clients with HttpClientFactory

Portanto, o que é um "cliente tipado"?So, what's a "Typed Client"? É apenas um HttpClient que é configurado na injeção pelo DefaultHttpClientFactory.It's just an HttpClient that's configured upon injection by the DefaultHttpClientFactory.

O diagrama a seguir mostra como os clientes tipados são usados com o HttpClientFactory:The following diagram shows how Typed Clients are used with HttpClientFactory:

Diagrama mostrando como os clientes digitados são usados com HttpClientFactory.

Figura 8-4.Figure 8-4. Use o HttpClientFactory com classes de cliente tipado.Using HttpClientFactory with Typed Client classes.

Na imagem acima, um ClientService (usado por um controlador ou código de cliente) usa um HttpClient criado pelo IHttpClientFactoryregistrado.In the above image, a ClientService (used by a controller or client code) uses an HttpClient created by the registered IHttpClientFactory. Essa fábrica atribui o HttpClient um HttpMessageHandler de um pool que ele gerencia.This factory assigns the HttpClient an HttpMessageHandler from a pool it manages. O HttpClient pode ser configurado com as políticas do Polly ao registrar o IHttpClientFactory no contêiner DI com o método de extensão AddHttpClient.The HttpClient can be configured with Polly's policies when registering the IHttpClientFactory in the DI container with the extension method AddHttpClient.

Para configurar a estrutura acima, adicione HttpClientFactory em seu aplicativo instalando o pacote NuGet Microsoft.Extensions.Http que inclui o método de extensão AddHttpClient() para IServiceCollection.To 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. Esse método de extensão registra o DefaultHttpClientFactory a ser usado como um singleton da interface IHttpClientFactory.This extension method registers the DefaultHttpClientFactory to be used as a singleton for the interface IHttpClientFactory. Ele define uma configuração transitória para o HttpMessageHandlerBuilder.It defines a transient configuration for the HttpMessageHandlerBuilder. Esse manipulador de mensagens (objeto HttpMessageHandler), obtido de um pool, é usado pelo HttpClient retornado do alocador.This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.

No próximo código, veja como AddHttpClient() pode ser usado para registrar clientes tipados (agentes de serviço) que precisam usar 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>();

O registro dos serviços do cliente, conforme mostrado no código anterior, torna o DefaultClientFactory criar um HttpClient padrão para cada serviço.Registering the client services as shown in the previous code, makes the DefaultClientFactory create a standard HttpClient for each service.

Você também pode adicionar a configuração específica da instância no registro para, por exemplo, configurar o endereço base e adicionar algumas políticas de resiliência, conforme mostrado no código a seguir: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());

Apenas para o exemplo, você pode ver uma das políticas acima no próximo código: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)));
}

Você pode encontrar mais detalhes sobre como usar o Polly no próximo artigo.You can find more details about using Polly in the Next article.

Tempos de vida de HttpClientHttpClient lifetimes

Sempre que você receber um objeto HttpClient do IHttpClientFactory, uma nova instância será retornada.Each time you get an HttpClient object from the IHttpClientFactory, a new instance is returned. Mas cada HttpClient usa um HttpMessageHandler que foi colocado em pool e reutilizado pelo IHttpClientFactory para reduzir o consumo de recursos, desde que o tempo de vida do HttpMessageHandler não tenha expirado.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.

O pooling de manipuladores é interessante porque cada manipulador normalmente gerencia suas próprias conexões de HTTP subjacentes. Criar mais manipuladores do que o necessário pode resultar em atrasos de conexão.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. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja a alterações de DNS.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

Os objetos HttpMessageHandler no pool têm um tempo de vida que é o período de tempo em que uma instância HttpMessageHandler no pool pode ser reutilizada.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. O valor padrão é dois minutos, mas pode ser substituído por cliente tipado.The default value is two minutes, but it can be overridden per Typed Client. Para substituí-lo, chame SetHandlerLifetime() no IHttpClientBuilder que é retornado ao criar o cliente, como mostra o código a seguir: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));

Cada cliente tipado pode ter seu próprio valor de tempo de vida do manipulador configurado.Each Typed Client can have its own configured handler lifetime value. Defina o tempo de vida como InfiniteTimeSpan para desabilitar a expiração do manipulador.Set the lifetime to InfiniteTimeSpan to disable handler expiry.

Implementar suas classes de cliente tipado que usam o HttpClient injetado e configuradoImplement your Typed Client classes that use the injected and configured HttpClient

Como uma etapa anterior, você precisa ter definido suas classes de cliente tipado, como as classes no código de exemplo: 'BasketService', 'CatalogService', 'OrderingService', etc. Um cliente tipado é uma classe que aceita um objeto HttpClient (injetado por seu construtor) e o usa para chamar algum serviço HTTP remoto.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. Por exemplo: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;
    }
}

O cliente tipado (CatalogService, no exemplo) é ativado pela DI (injeção de dependência), o que significa que ele pode aceitar qualquer serviço registrado em seu construtor, além do 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.

Um cliente tipado é, efetivamente, um objeto transitório, o que significa que uma instância é criada sempre que necessário e recebe uma nova instância de HttpClient sempre que é construída.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. No entanto, os objetos HttpMessageHandler no pool são os objetos que são reutilizados por várias solicitações HTTP.However, the HttpMessageHandler objects in the pool are the objects that are reused by multiple Http requests.

Usar suas classes de cliente tipadoUse your Typed Client classes

Por fim, depois de implementar as classes tipadas e tê-las registradas com AddHttpClient(), você poderá usá-las sempre que puder ter os serviços injetados por 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. Por exemplo, em um código de página do Razor ou no controlador de um aplicativo Web MVC, como no código a seguir de 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
        }

        }
}

Até este ponto, o código mostrado está apenas executando solicitações HTTP regulares, mas a 'mágica' vem nas seções a seguir, em que, basta adicionar políticas e delegar manipuladores aos clientes tipados registrados, para que todas as solicitações HTTP realizadas por HttpClient se comportem considerando políticas resilientes, como repetições com retirada exponencial, disjuntores ou qualquer outro manipulador delegado personalizado para implementar recursos de segurança adicionais, como o uso de tokens de autenticação ou qualquer outro recurso personalizado.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.

Recursos adicionaisAdditional resources