Uso de IHttpClientFactory para implementar solicitudes HTTP resistentes
Sugerencia
Este contenido es un extracto del libro electrónico, ".NET Microservices Architecture for Containerized .NET Applications" (Arquitectura de microservicios de .NET para aplicaciones de .NET contenedorizadas), disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.
IHttpClientFactory es un contrato implementado por DefaultHttpClientFactory
, una fábrica bien fundamentada disponible desde .NET Core 2.1 para crear instancias de HttpClient con el fin de usarlas en las aplicaciones.
Problemas con la clase HttpClient original disponible en .NET
La clase de HttpClient original y conocida se puede usar fácilmente, pero en algunos casos, muchos desarrolladores no lo usan correctamente.
Aunque esta clase implementa IDisposable
, no se aconseja declarar y crear instancias de ella en una instrucción using
porque, cuando el objeto HttpClient
se desecha, el socket subyacente no se libera inmediatamente, lo que puede conducir a un problema de agotamiento del socket. Para obtener más información sobre este problema, consulte la entrada de blog Está usando HttpClient incorrecto y está desestabilizando el software.
Por lo tanto, HttpClient
está pensado para ser instanciado una vez y reutilizarse durante toda la vida útil de una aplicación. Crear una instancia de una clase HttpClient
para cada solicitud agotará el número de sockets disponibles bajo cargas pesadas. Ese problema generará errores SocketException
. Los posibles enfoques para resolver ese problema se basan en la creación del objeto HttpClient
como singleton o estático, como se explica en este artículo de Microsoft sobre el uso de HttpClient. Puede ser una buena solución para aplicaciones de consola de corta duración o similares, que se ejecutan varias veces al día.
Otro problema con el que se encuentran los desarrolladores es al utilizar una instancia compartida de HttpClient
en procesos de ejecución prolongada. En una situación en la que se crean instancias del HttpClient como un singleton o un objeto estático, los cambios de DNS no se pueden controlar, tal y como se describe en esta incidencia del repositorio de GitHub sobre dotnet/runtime.
Sin embargo, el problema no está realmente con HttpClient
por se, pero con el constructor predeterminado para HttpClient, ya que crea una nueva instancia concreta de HttpMessageHandler, que es la que tiene problemas de agotamiento de sockets y cambios de DNS mencionados anteriormente.
Para solucionar los problemas mencionados anteriormente y para que las instancias de HttpClient
puedan administrarse, .NET Core 2.1 introdujo dos enfoques, uno de ellos se IHttpClientFactory. Se trata de una interfaz que se usa para configurar y crear instancias de HttpClient
en una aplicación mediante la inserción de dependencias (DI). También proporciona extensiones para el middleware basado en Polly para aprovechar las ventajas de delegar controladores en HttpClient.
La alternativa es usar SocketsHttpHandler
con PooledConnectionLifetime
configurado. Este enfoque se aplica a instancias de larga duración ,static
o HttpClient
de singleton. Para obtener más información sobre las distintas estrategias, vea Directrices de HttpClient para .NET.
Polly es una biblioteca de manejo de fallos transitorios que ayuda a los desarrolladores a agregar resiliencia a sus aplicaciones mediante el uso de algunas directivas predefinidas de forma fluida y segura para subprocesos.
Ventajas de usar IHttpClientFactory
La implementación actual de IHttpClientFactory, que también implementa IHttpMessageHandlerFactory, ofrece las siguientes ventajas:
- Proporciona una ubicación central para asignar nombres y configurar objetos de
HttpClient
lógicos. Por ejemplo, puede configurar un cliente (Agente de servicio) preconfigurado para acceder a un microservicio específico. - Codifique el concepto de middleware saliente mediante la delegación de manejadores en
HttpClient
e implemente middleware basado en Polly para aprovechar las políticas de Polly y lograr resiliencia. -
HttpClient
ya posee el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes. Los clientes HTTP se pueden registrar en la fábrica y se puede usar un controlador de Polly que permite utilizar directivas de Polly para el reintento, interruptores, etc. - Administre la duración de HttpMessageHandler para evitar los problemas mencionados y los que se puedan producir al administrar las duraciones de
HttpClient
usted mismo.
Sugerencia
Las instancias de HttpClient
insertadas mediante DI se pueden eliminar de forma segura, porque el elemento HttpMessageHandler
asociado lo administra la fábrica. Las instancias de HttpClient
insertadas son transitorias desde punto de vista de DI, mientras que las de HttpMessageHandler
se pueden considerar como con ámbito. Las instancias de HttpMessageHandler
tienen sus propios ámbitos de DI, independientes de los ámbitos de la aplicación (por ejemplo, ámbitos de solicitud de entrada de ASP.NET). Para obtener más información, vea Using HttpClientFactory in .NET.
Nota
La implementación de IHttpClientFactory
(DefaultHttpClientFactory
) está estrechamente vinculada a la implementación de DI en el paquete NuGet de Microsoft.Extensions.DependencyInjection
. Si necesita usar HttpClient
sin DI o con otras implementaciones de DI, considere la posibilidad de usar un elemento static
o una singleton HttpClient
con la configuración PooledConnectionLifetime
. A fin de obtener más información, vea Directrices de HttpClient para .NET.
Varias maneras de usar IHttpClientFactory
Hay varias maneras de usar IHttpClientFactory
en la aplicación:
- Uso básico
- Usar clientes con nombre.
- Usar clientes con tipo.
- Uso de clientes generados
Por motivos de brevedad, esta guía muestra la manera más estructurada de usar IHttpClientFactory
, que consiste en utilizar clientes tipados (patrón de agente de servicios). Pero todas las opciones están documentadas e incluidas actualmente en este artículo que trata sobre el uso de HttpClientFactoryIHttpClientFactory
.
Nota
Si la aplicación requiere cookies, es posible que sea mejor evitar el uso de IHttpClientFactory en la aplicación. Para obtener formas alternativas de administrar clientes, consulte Directrices para usar clientes HTTP.
Cómo usar Clientes tipados con IHttpClientFactory
Así pues, ¿qué es un "Cliente con tipo"? Es solo un elemento HttpClient
que está preconfigurado para un uso específico. Esta configuración puede incluir valores específicos, como el servidor base, los encabezados HTTP o los tiempos de espera.
En el diagrama siguiente se muestra cómo se usan los clientes con tipo con IHttpClientFactory
:
Figura 8-4. Uso de IHttpClientFactory
con clases de cliente con tipo.
En la imagen anterior, un servicio ClientService
(usado por un controlador o código de cliente) utiliza un cliente HttpClient
creado por la fábrica IHttpClientFactory
registrada. Este generador asigna a HttpClient
un elemento HttpMessageHandler
desde un grupo. El cliente HttpClient
se puede configurar con las directivas de Polly al registrar la fábrica IHttpClientFactory
en el contenedor de DI con el método de extensión AddHttpClient.
Para configurar la estructura anterior, agregue IHttpClientFactory en la aplicación instalando el paquete NuGet de Microsoft.Extensions.Http
que incluye el método de extensión AddHttpClient para IServiceCollection. Este método de extensión registra la clase DefaultHttpClientFactory
interna que se usará como singleton para la interfaz IHttpClientFactory
. Define una configuración transitoria para el HttpMessageHandlerBuilder. Este controlador de mensajes (el objeto HttpMessageHandler), tomado de un grupo, lo usa el HttpClient
devuelto desde la fábrica.
En el siguiente fragmento de código, puede ver cómo se puede usar AddHttpClient()
para registrar clientes tipados (agentes de servicio) que deben 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>();
Al registrar los servicios de cliente como se muestra en el fragmento de código anterior, el DefaultClientFactory
crea un HttpClient
estándar para cada servicio. El cliente con tipo se registra como transitorio con contenedor DI (de inserción con dependencia). En el código anterior, AddHttpClient()
registra CatalogService, BasketService, OrderingService como servicios transitorios para que se puedan insertar y consumir directamente sin necesidad de registros adicionales.
También puede agregar una configuración específica de instancia en el registro para, por ejemplo, configurar la dirección base y agregar algunas directivas de resistencia, como se muestra en lo siguiente:
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
En este ejemplo siguiente, puede ver la configuración de una de las directivas anteriores:
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
Puede encontrar más detalles sobre el uso de Polly en el artículo siguiente.
Vigencias de HttpClient
Cada vez que obtiene un objeto HttpClient
del IHttpClientFactory
, recibe una nueva instancia. Sin embargo, cada HttpClient
usa una HttpMessageHandler
agrupada y reutilizada por el IHttpClientFactory
para reducir el consumo de recursos, siempre y cuando la vida útil del HttpMessageHandler
no haya expirado.
La agrupación de controladores es deseable, ya que cada controlador normalmente administra sus propias conexiones HTTP subyacentes; la creación de más controladores de los necesarios puede dar lugar a retrasos de conexión. Algunos controladores también mantienen abiertas las conexiones indefinidamente, lo que puede impedir que el controlador reaccione a los cambios de DNS.
Los objetos HttpMessageHandler
del grupo tienen una duración que es el período de tiempo que se puede reutilizar una instancia de HttpMessageHandler
en el grupo. El valor predeterminado es de dos minutos, pero se puede invalidar por cada cliente con tipo. Para ello, llame a SetHandlerLifetime()
en el IHttpClientBuilder que se devuelve cuando se crea el cliente, como se muestra en el siguiente código:
//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 con tipo puede tener configurado su propio valor de duración de controlador. Establezca la duración en InfiniteTimeSpan
para deshabilitar la expiración del controlador.
Implemente las clases de cliente tipadas que usan el HttpClient inyectado y configurado.
Como paso anterior, debe tener definidas las clases de cliente con tipo, como las clases del código de ejemplo, como "BasketService", "CatalogService", "OrderingService", etc. – Un cliente con tipo es una clase que acepta un objeto HttpClient
(insertado a través de su constructor) y lo usa para llamar a algún servicio HTTP remoto. Por ejemplo:
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;
}
}
El Typed Client (CatalogService
en el ejemplo) se activa mediante DI (Dependency Injection), lo que significa que puede aceptar cualquier servicio registrado en su constructor, además de HttpClient
.
Un cliente tipado es efectivamente un objeto transitorio, lo que significa que se crea una nueva instancia cada vez que se necesita. Recibe una nueva instancia de HttpClient
cada vez que se construye. Pero los objetos HttpMessageHandler
del grupo son los objetos que varias instancias de HttpClient
reutilizan.
Usar las clases de cliente con tipo
Por último, una vez que haya implementado las clases tipadas, puede registrarlas y configurarlas con AddHttpClient()
. Después de eso, puede usarlos dondequiera que los servicios se inserten mediante DI, como en el código de la página de Razor o en un controlador de aplicación web MVC, que se muestra en el código siguiente 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
}
}
}
Hasta este punto, el fragmento de código anterior solo muestra el ejemplo de realizar solicitudes HTTP normales. Pero la "magia" se encuentra en las siguientes secciones, donde muestra cómo todas las solicitudes HTTP realizadas por HttpClient
pueden tener políticas resilientes, como reintentos con retroceso exponencial, interruptores automáticos, características de seguridad mediante tokens de autenticación o incluso cualquier otra característica personalizada. Y todo esto se puede hacer simplemente agregando directivas y delegando controladores a los clientes con tipo registrados.
Recursos adicionales
Directrices de HttpClient para .NEThttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
Uso de HttpClientFactory en .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
Uso de HttpClientFactory en ASP.NET Corehttps://learn.microsoft.com/aspnet/core/fundamentals/http-requests
Código fuente de HttpClientFactory en el repositorio de GitHub
dotnet/runtime
https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (biblioteca de resiliencia y manejo de errores transitorios de .NET)https://thepollyproject.azurewebsites.net/