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.
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 IHttpClientFactory
o , 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
:
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 HttpClient
o .
// 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 HttpMessageHandler
vida ú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
Diretrizes HttpClient para .NET
https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelinesUsando 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 HttpClientFactory no
dotnet/runtime
repositório GitHub
https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (resiliência do .NET e biblioteca de tratamento de falhas transitórias)
https://thepollyproject.azurewebsites.net/