Compartilhar via


Usar IHttpClientFactory para implementar solicitações HTTP resilientes

Dica

Esse conteúdo é um trecho do eBook, arquitetura de microsserviços do .NET para aplicativos .NET em contêineres, disponível em do .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

Miniatura da capa do livro eletrônico Arquitetura de microsserviços do .NET para aplicativos .NET conteinerizados.

IHttpClientFactory é um contrato implementado por DefaultHttpClientFactory, um alocador "teimoso", disponível desde o .NET Core 2.1, para a criação de instâncias do HttpClient a serem usadas em seus aplicativos.

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

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

Embora essa classe implemente IDisposable, declarar e instanciá-la dentro de uma instrução using não é preferível porque quando o objeto HttpClient é descartado, o soquete subjacente 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 no blog Você está usando HttpClient errado e isso está desestabilizando seu software.

Portanto, HttpClient deve ser instanciado uma vez e reutilizado ao longo da vida útil de um aplicativo. 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. Esse problema resultará em erros de SocketException. As abordagens possíveis para resolver esse 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. Essa pode ser uma boa solução para aplicativos de console de curta duração ou similares, que são executados algumas vezes por dia.

Outro problema que os desenvolvedores encontram é ao usar uma instância compartilhada de HttpClient em processos de longa duraçã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 do dotnet/runtime.

No entanto, o problema não é realmente com HttpClient propriamente dito, mas com o construtor padrão para HttpClient, porque ele cria uma nova instância concreta de HttpMessageHandler, que é aquela que tem soquetes de esgotamento 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. Trata-se de uma interface usada para configurar e criar instâncias HttpClient em um aplicativo por meio de DI (Injeção de Dependência). Ela também fornece extensões para o middleware baseado em Polly para aproveitar a delegação de manipuladores no HttpClient.

A alternativa é usar SocketsHttpHandler com PooledConnectionLifetime configurado. Essa abordagem é aplicada a instâncias de longa duração, static ou HttpClient singleton. Para saber mais sobre estratégias diferentes, confira Diretrizes de HttpClient para .NET.

Polly é uma biblioteca de manipulação de falhas transitórias que ajuda os desenvolvedores a adicionar resiliência aos seus aplicativos, usando algumas políticas predefinidas de maneira fluente e seguro para threads.

Benefícios do uso de IHttpClientFactory

A implementação atual de 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.
  • Codifica 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.
  • HttpClient já tem o conceito de delegar manipuladores que podem ser vinculados uns aos outros 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 Repetição, CircuitBreakers etc.
  • Gerencie o ciclo de vida de HttpMessageHandler para evitar os problemas mencionados que podem ocorrer ao gerenciar você mesmo o ciclo de vida de HttpClient.

Dica

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

Nota

A implementação de IHttpClientFactory (DefaultHttpClientFactory) está fortemente vinculada à implementação de DI no pacote NuGet Microsoft.Extensions.DependencyInjection. Se você precisar usar HttpClient sem DI ou com outras implementações de DI, considere usar um static ou um HttpClient do tipo singleton com o PooledConnectionLifetime já configurado. Para obter mais informações, confira Diretrizes de HttpClient para .NET.

Várias maneiras de usar IHttpClientFactory

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

  • Uso básico
  • Usar clientes nomeados
  • Usar clientes tipados
  • Usar clientes gerados

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

Nota

Se seu aplicativo exigir cookies, talvez seja melhor evitar o uso de IHttpClientFactory em seu aplicativo. Para obter maneiras alternativas de gerenciar clientes, consulte Diretrizes para usar clientes HTTP.

Como usar clientes tipados com IHttpClientFactory

Portanto, 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 limite.

O diagrama a seguir mostra como os clientes tipados são usados com o IHttpClientFactory:

O diagrama a seguir mostra como os clientes tipados são usados com o IHttpClientFactory.

Figura 8-4. Usando IHttpClientFactory com classes de cliente tipado.

Na imagem acima, um ClientService (usado por um controlador ou código do cliente) usa um HttpClient criado pelo IHttpClientFactory registrado. Esta fábrica atribui um HttpMessageHandler de um pool para o HttpClient. O HttpClient pode ser configurado com políticas da Polly ao registrar o IHttpClientFactory no contêiner de 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. Esse método de extensão registra a classe interna DefaultHttpClientFactory para ser usada como um singleton na interface IHttpClientFactory. Ele define uma configuração transitória para o HttpMessageHandlerBuilder. Esse manipulador de mensagens (objeto HttpMessageHandler), obtido de um pool, é usado pelo HttpClient retornado do alocador.

No próximo snippet, você pode ver como AddHttpClient() podem ser usados para registrar 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 cliente, conforme mostrado no snippet anterior, faz com que o DefaultClientFactory crie um HttpClient padrão para cada serviço. O cliente tipado é registrado como transitório com o contêiner de DI. No código anterior, AddHttpClient() registra CatalogService, BasketService, OrderingService como serviços transitórios para que possam ser injetados e consumidos diretamente sem a necessidade de registros adicionais.

Você também pode adicionar uma configuração específica de instância no registro para, por exemplo, configurar o endereço base e adicionar algumas políticas de resiliência, conforme mostrado no seguinte:

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

Veja mais detalhes sobre como usar Polly no próximo artigo.

Tempo de vida do HttpClient

Sempre que você obtém um objeto HttpClient do IHttpClientFactory, uma nova instância é retornada. 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.

O pool 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 corresponde ao período durante o qual uma instância HttpMessageHandler no pool pode ser reutilizada. O valor padrão é dois minutos, mas pode ser substituído por cliente tipado. Para substituí-lo, chame SetHandlerLifetime() no IHttpClientBuilder que é retornado ao criar o cliente, como mostra o 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 tempo de vida do manipulador configurado. Defina o tempo de vida como InfiniteTimeSpan para desabilitar a expiração do manipulador.

Implementar suas classes de cliente tipado que usam o HttpClient injetado e configurado

Como uma etapa anterior, você precisa ter suas classes cliente tipadas definidas, como as classes no código de exemplo, como 'BasketService', 'CatalogService', 'OrderingService', etc. – Um Cliente Digitado é uma classe que aceita um objeto HttpClient (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 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.

Um Cliente Tipado é efetivamente um objeto transitório, o que significa que uma nova instância é criada sempre que uma é necessária. Ela recebe uma nova instância de HttpClient cada vez que é construída. 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 tipado

Por fim, depois de implementar suas classes tipadas, você poderá registrá-las e configurá-las com AddHttpClient(). Depois disso, você pode usá-los onde quer que os serviços sejam injetados por DI, como no código da página razor ou em um controlador de aplicativo Web MVC, mostrado no código abaixo de 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é esse ponto, o snippet 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, em que mostra como todas as solicitações HTTP feitas por HttpClient podem ter políticas resilientes, como repetições com retirada 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 para seus Clientes Tipados registrados.

Recursos adicionais