Use IHttpClientFactory para implementar solicitações HTTP resilientes

Gorjeta

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

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

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

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

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

Embora essa classe implemente IDisposable, declará-lo e instanciá-lo dentro de uma using instrução não é preferível porque quando o HttpClient objeto é descartado, o soquete subjacente não é liberado imediatamente, o que pode levar a um problema de exaustão 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 HttpClient classe para cada solicitação esgotará o número de soquetes disponíveis sob cargas pesadas. Esse problema resultará em SocketException erros. As abordagens possíveis para resolver esse problema são baseadas na criação do objeto como singleton ou estático, conforme explicado neste artigo da Microsoft sobre o uso do HttpClient 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 nesta edição do repositório GitHub dotnet/runtime.

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

Para resolver os problemas mencionados acima e tornar HttpClient as instâncias gerenciáveis, o .NET Core 2.1 introduziu duas abordagens, sendo uma delas o IHttpClientFactory. É uma interface usada para configurar e criar HttpClient instâncias em um aplicativo 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 SocketsHttpHandler com configurado PooledConnectionLifetime. Essa 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 lógicos HttpClient . 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 e da implementação de middleware baseado em HttpClient 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 do HttpMessageHandler para evitar os problemas/problemas mencionados que podem ocorrer ao gerenciar HttpClient vidas por conta própria.

Gorjeta

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

Nota

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

Várias maneiras de usar IHttpClientFactory

Há várias maneiras que você pode usar 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 IHttpClientFactoryo , que é usar Clientes Tipados (padrão do Service Agent). No entanto, todas as opções estão documentadas e estão listadas neste artigo que abrange o IHttpClientFactory uso.

Nota

Se o seu aplicativo requer cookies, talvez seja melhor evitar o uso IHttpClientFactory no seu aplicativo. 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 Digitado"? É apenas um HttpClient que é 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 a seguir mostra como os clientes digitados são usados com IHttpClientFactory:

Diagram showing how typed clients are used with IHttpClientFactory.

Figura 8-4. Usando IHttpClientFactory com classes Typed Client.

Na imagem acima, um ClientService (usado por um controlador ou código de cliente) usa um HttpClient criado pelo registrado IHttpClientFactory. Esta fábrica atribui um HttpMessageHandler de uma piscina 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 AddHttpClientde extensão.

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

No próximo trecho, você pode ver como AddHttpClient() pode ser usado para registrar clientes tipados (agentes de serviço) que precisam usar HttpCliento .

// 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, torna a DefaultClientFactory criação um padrão HttpClient para cada serviço. O cliente tipado é registrado como transitório com o contêiner DI. No código anterior, registra CatalogService, BasketService, OrderingService como serviços transitórios para que possam ser injetados e consumidos diretamente, AddHttpClient()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 próximo artigo.

Tempos de vida do HttpClient

Cada vez que você obtém um HttpClient objeto do IHttpClientFactory, uma nova instância é retornada. Mas cada HttpClient um usa um HttpMessageHandler que é agrupado e reutilizado pelo para reduzir o IHttpClientFactory consumo de recursos, desde que a HttpMessageHandlervida útil do nã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 HttpMessageHandler objetos no pool têm um tempo de vida que é o período de tempo que uma HttpMessageHandler instância no pool pode ser reutilizada. O valor padrão é dois minutos, mas pode ser substituído por cliente digitado. Para substituí-lo, chame SetHandlerLifetime() o IHttpClientBuilder que é 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. Defina 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 HttpClient objeto (injetado por meio 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 ele pode aceitar qualquer serviço registrado em 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 HttpClient instância cada vez que é construído. No entanto, os HttpMessageHandler objetos no pool são os objetos que são reutilizados por várias HttpClient instâncias.

Usar suas classes de cliente digitado

Finalmente, depois de implementar suas classes digitadas, você pode registrá-las e configurá-las com AddHttpClient()o . 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 a seguir, 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 adicionando políticas e delegando manipuladores aos seus clientes digitados registrados.

Recursos adicionais