Partilhar via


Use IHttpClientFactory para implementar solicitações HTTP resilientes

Dica

Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no do .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

Miniatura da capa do eBook .NET Microservices Architecture for Containerized .NET Applications.

IHttpClientFactory é um contrato implementado pela DefaultHttpClientFactory, uma fábrica opinativa, disponível desde o .NET Core 2.1, para criar instâncias de HttpClient a serem usadas em seus aplicativos.

Problemas com a classe HttpClient original disponível no .NET

A classe HttpClient original e bem conhecida pode ser facilmente usada, mas em alguns casos, não está sendo usada corretamente por muitos desenvolvedores.

Embora esta classe implemente IDisposable, declarar e instanciá-la dentro de uma instrução using não é recomendado porque, quando o objeto HttpClient é descartado, o soquete subjacente ao não é liberado imediatamente, o que pode levar a um problema de esgotamento do soquete . Para obter mais informações sobre esse problema, consulte a postagem do blog Você está usando o HttpClient errado e está desestabilizando seu software.

Portanto, HttpClient destina-se a ser instanciado uma vez e reutilizado durante toda a vida útil de um aplicativo. Instanciar uma classe HttpClient para cada solicitação esgotará o número de soquetes disponíveis sob cargas pesadas. Esse problema resultará em erros de SocketException. As abordagens possíveis para resolver esse problema são baseadas na criação do objeto HttpClient como singleton ou estático, conforme explicado neste artigo da Microsoft sobre o uso do HttpClient. Esta pode ser uma boa solução para aplicativos de console de curta duração ou similares, que são executados algumas vezes ao dia.

Outro problema que os desenvolvedores enfrentam é ao usar uma instância compartilhada de HttpClient em processos de longa execução. Em uma situação em que o HttpClient é instanciado como um singleton ou um objeto estático, ele falha ao lidar com as alterações de DNS conforme descrito neste problema do repositório GitHub dotnet/runtime.

No entanto, o problema não é realmente com HttpClient em si, mas com o construtor padrão para HttpClient, porque ele cria uma nova instância concreta de HttpMessageHandler, que é a que tem de esgotamento de soquetes e problemas de alterações de DNS mencionados acima.

Para resolver os problemas mencionados acima e tornar HttpClient instâncias gerenciáveis, o .NET Core 2.1 introduziu duas abordagens, sendo uma delas IHttpClientFactory. É uma interface usada para configurar e criar instâncias de HttpClient numa aplicação por meio da injeção de dependência (DI). Ele também fornece extensões para middleware baseado em Polly para tirar proveito da delegação de manipuladores em HttpClient.

A alternativa é usar o SocketsHttpHandler com o PooledConnectionLifetimeconfigurado. Esta abordagem é aplicada a instâncias de longa duração static ou singleton HttpClient. Para saber mais sobre diferentes estratégias, consulte HttpClient guidelines for .NET.

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 maneira fluente e segura para threads.

Benefícios de usar IHttpClientFactory

A atual implementação do IHttpClientFactory, que também implementa IHttpMessageHandlerFactory, oferece os seguintes benefícios:

  • Fornece um local central para nomear e configurar objetos HttpClient lógicos. Por exemplo, você pode configurar um cliente (Service Agent) pré-configurado para acessar um microsserviço específico.
  • Codificar o conceito de middleware de saída por meio da delegação de manipuladores em HttpClient e implementação de middleware baseado em Polly para aproveitar as políticas de resiliência da Polly.
  • HttpClient já tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída. Você pode registrar clientes HTTP na fábrica e pode usar um manipulador Polly para usar políticas Polly para Retry, CircuitBreakers e assim por diante.
  • Gerencie o tempo de vida de HttpMessageHandler para evitar os problemas/problemas mencionados que podem ocorrer ao gerenciar HttpClient vidas por conta própria.

Dica

As HttpClient instâncias injetadas pelo DI podem ser descartadas com segurança, pois o HttpMessageHandler associado é gerenciado pela fábrica. Instâncias injetadas de HttpClient são transitórias de uma perspetiva de DI, enquanto instâncias HttpMessageHandler podem ser consideradas com escopo. HttpMessageHandler instâncias têm seus próprios escopos de DI separam dos escopos de aplicativo (por exemplo, ASP.NET escopos de solicitação de entrada). Para obter mais informações, consulte Utilizando HttpClientFactory no .NET.

Observação

A implementação de IHttpClientFactory (DefaultHttpClientFactory) está intimamente ligada à implementação de DI no pacote NuGet Microsoft.Extensions.DependencyInjection. Se precisar usar HttpClient sem DI ou com outras implementações de DI, considere usar um static ou singleton HttpClient com PooledConnectionLifetime configurado. Para obter mais informações, consulte HttpClient guidelines for .NET.

Várias maneiras de usar IHttpClientFactory

Há várias maneiras de usáIHttpClientFactory em seu aplicativo:

  • Utilização básica
  • Usar clientes nomeados
  • Usar clientes digitados
  • Usar clientes gerados

Por uma questão de brevidade, esta orientação mostra a maneira mais estruturada de usar IHttpClientFactory, que é usar Clientes Tipados (padrão do Service Agent). No entanto, todas as opções estão documentadas e estão atualmente listadas neste artigo que cobre o uso IHttpClientFactory.

Observação

Se a sua aplicação necessitar de cookies, poderá ser melhor evitar a utilização de IHttpClientFactory na sua aplicação. Para obter formas alternativas de gerenciar clientes, consulte Diretrizes para usar clientes HTTP.

Como usar clientes tipados com IHttpClientFactory

Então, o que é um "Cliente Tipado"? É apenas um HttpClient pré-configurado para algum uso específico. Essa configuração pode incluir valores específicos, como o servidor base, cabeçalhos HTTP ou tempos limites.

O diagrama seguinte mostra como os Clientes Tipados são usados com IHttpClientFactory:

Diagrama que mostra como os clientes tipados são usados com IHttpClientFactory.

Figura 8-4. Usando IHttpClientFactory com classes de Cliente Tipado.

Na imagem acima, um ClientService (usado por um controlador ou código de cliente) usa um HttpClient criado pelo IHttpClientFactoryregistrado. Esta fábrica atribui um HttpMessageHandler de um conjunto para o HttpClient. 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.

Para configurar a estrutura acima, adicione IHttpClientFactory em seu aplicativo instalando o pacote NuGet Microsoft.Extensions.Http que inclui o método de extensão AddHttpClient para IServiceCollection. Este método de extensão registra a classe DefaultHttpClientFactory interna a ser usada como um singleton para a interface IHttpClientFactory. Ele define uma configuração transitória para o HttpMessageHandlerBuilder. Este manipulador de mensagens (objetoHttpMessageHandler), retirado de um pool, é usado pelo HttpClient retornado de fábrica.

No próximo trecho, pode ver como AddHttpClient() pode ser utilizado para registar Clientes Tipados (Agentes de Serviço) que precisam usar 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>();

Registrar os serviços do cliente, como mostrado no trecho anterior, faz com que o DefaultClientFactory crie um HttpClient padrão para cada serviço. O cliente com tipo definido é registado como transitório com o contentor DI. No código anterior, AddHttpClient() registra CatalogService, BasketService, OrderingService como serviços transitórios para que possam ser injetados e consumidos diretamente sem qualquer necessidade de registros adicionais.

Você também pode adicionar 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 a seguir:

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

Neste próximo exemplo, você pode ver a configuração de uma das políticas acima:

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 o uso do Polly no artigo próximo .

Tempos de vida do HttpClient

Cada vez que você obtém um objeto HttpClient do IHttpClientFactory, uma nova instância é retornada. Mas cada HttpClient usa um HttpMessageHandler que é agrupado e reutilizado pelo IHttpClientFactory para reduzir o consumo de recursos, desde que a vida útil do HttpMessageHandlernão tenha expirado.

O agrupamento de manipuladores é desejável, pois cada manipulador normalmente gerencia suas próprias conexões HTTP subjacentes; Criar mais manipuladores do que o necessário pode resultar em atrasos de conexão. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja às alterações de DNS.

Os objetos HttpMessageHandler no pool têm um tempo de vida que é o período de tempo que uma instância HttpMessageHandler no pool pode ser reutilizada. O valor padrão é de dois minutos, mas pode ser alterado para cada Typed Client. Para substituí-lo, chame SetHandlerLifetime() no IHttpClientBuilder retornado ao criar o cliente, conforme mostrado no código a seguir:

//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));

Cada cliente tipado pode ter seu próprio valor de vida útil do manipulador configurado. Configure o tempo de vida como InfiniteTimeSpan para desativar a expiração do manipulador.

Implemente suas classes Typed Client que usam o HttpClient injetado e configurado

Como etapa anterior, você precisa ter suas classes de Cliente Tipado definidas, como as classes no código de exemplo, como 'BasketService', 'CatalogService', 'OrderingService', etc. – Um Cliente Tipado é uma classe que aceita um objeto HttpClient (injetado através de seu construtor) e o usa para chamar algum serviço HTTP remoto. Por exemplo:

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 por DI (Injeção de Dependência), o que significa que pode aceitar qualquer serviço registrado no seu construtor, além de HttpClient.

Um cliente tipado é efetivamente um objeto transitório, o que significa que uma nova instância é criada sempre que uma é necessária. Ele recebe uma nova instância de HttpClient cada vez que é construído. No entanto, os objetos HttpMessageHandler no pool são os objetos que são reutilizados por várias instâncias HttpClient.

Usar suas classes de cliente tipadas

Finalmente, depois de ter suas classes digitadas implementadas, você pode tê-las registradas e configuradas com AddHttpClient(). Depois disso, você pode usá-los sempre que os serviços forem injetados pelo DI, como no código de página do Razor ou em um controlador de aplicativo Web MVC, mostrado no código abaixo do 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 trecho de código acima mostra apenas o exemplo de execução de solicitações HTTP regulares. Mas a "mágica" vem nas seções seguintes, onde mostra como todas as solicitações HTTP feitas por HttpClient podem ter políticas resilientes, como repetições com backoff exponencial, disjuntores, recursos de segurança usando tokens de autenticação ou até mesmo qualquer outro recurso personalizado. E tudo isso pode ser feito apenas ao adicionar políticas e delegar handlers aos seus Typed Clients registados.

Recursos adicionais