Use o IHttpClientFactory para implementar solicitações HTTP resilientes
Dica
Esse conteúdo é um trecho do eBook da Arquitetura de Microsserviços do .NET para os Aplicativos .NET em Contêineres, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.
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 usada com facilidade, mas em alguns casos, muitos desenvolvedores não a usam corretamente.
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 saber mais 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).
Portanto, HttpClient
deve ser instanciado uma única vez e reutilizado durante a 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
. 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. Essa pode ser uma boa solução para aplicativos de console de curta duração ou semelhantes, que são executados algumas vezes por 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 não consegue lidar com as alterações de DNS, conforme descrito neste problema do repositório GitHub dotnet/runtime.
No entanto, o problema não está realmente no HttpClient
em si, mas com o construtor padrão para HttpClient, pois ele cria uma nova instância concreta de HttpMessageHandler, que é aquela que enfrenta o esgotamento de soquetes e os problemas de alterações de DNS mencionados acima.
Para resolver os problemas mencionados acima e tornar as instâncias HttpClient
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 thread-safe.
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 lógicos de
HttpClient
. Por exemplo, você pode configurar um cliente (agente de serviço) 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. - O
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 tempo de vida de HttpMessageHandler para evitar os problemas mencionado que podem ocorrer ao gerenciar tempos de vida do
HttpClient
por conta própria.
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 Como usar HttpClientFactory no .NET.
Observação
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 HttpClient
singleton com PooledConnectionLifetime
configurado. Para obter mais informações, confira Diretrizes de HttpClient para .NET.
Várias maneiras de usar IHttpClientFactory
Há várias maneiras de usar o IHttpClientFactory
no aplicativo:
- Uso básico
- Usar clientes nomeados
- Usar clientes tipados
- Usar clientes gerados
Para fins de brevidade, essas diretrizes mostram a maneira mais estruturada de usar IHttpClientFactory
, que é usar clientes tipados (padrão do agente de serviço). No entanto, todas as opções estão documentadas e listadas neste artigo que abrange o uso de IHttpClientFactory
.
Observação
Se seu aplicativo exigir cookies, talvez seja melhor evitar o uso de IHttpClientFactory nele. 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
:
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 DefaultHttpClientFactory
interna a ser usada como um singleton da 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, veja como AddHttpClient()
pode ser usado 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>();
O registro dos serviços do 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 à 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)));
}
Veja mais detalhes sobre como usar Polly no próximo artigo.
Tempos de vida de HttpClient
Sempre que você receber um objeto HttpClient
do IHttpClientFactory
, uma nova instância será 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 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. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja a alterações de DNS.
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. 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 definir 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. 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, ou seja, uma nova instância é criada sempre que há a necessidade. Ele recebe uma nova instância HttpClient
sempre 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, após a implementação de suas classes tipadas, você poderá registrá-las e configurá-las com AddHttpClient()
. Depois disso, você poderá 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é este ponto, o snippet de código acima mostra apenas o exemplo de execução de solicitações HTTP regulares. Mas a "mágica" acontece 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 aos seus Clientes Tipados registrados.
Recursos adicionais
Diretrizes do HttpClient para .NET
https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelinesComo usar HttpClientFactory no .NET
https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factoryUsando HttpClientFactory no ASP.NET Core
https://learn.microsoft.com/aspnet/core/fundamentals/http-requestsCódigo-fonte de HttpClientFactory no repositório
dotnet/runtime
de GitHub
https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (biblioteca de tratamento de falhas transitórias e resiliência do .NET)
https://thepollyproject.azurewebsites.net/