Использование IHttpClientFactory для реализации устойчивых HTTP-запросов
Совет
Это фрагмент из электронной книги "Архитектура микросервисов .NET для контейнеризованных приложений .NET", доступна в документации .NET или в формате бесплатного PDF, доступного для скачивания, который можно читать офлайн.
IHttpClientFactory — это контракт, реализованный DefaultHttpClientFactory
, концептуальной фабрикой, доступной начиная с .NET Core 2.1, для создания экземпляров HttpClient, используемых в ваших приложениях.
Проблемы с исходным классом HttpClient, доступным в .NET
Исходный и известный класс HttpClient можно легко использовать, но в некоторых случаях он не используется должным образом многими разработчиками.
Хотя этот класс реализует IDisposable
, объявлять и создавать его экземпляры в операторе using
не рекомендуется, так как при удалении объекта HttpClient
базовый сокет не сразу освобождается, что может привести к проблеме исчерпания сокета. Дополнительные сведения об этой проблеме см. в посте в блоге Вы используете HttpClient неправильно, и это дестабилизирует ваше программное обеспечение.
Таким образом, HttpClient
предназначено для инициализации один раз и повторного использования на протяжении всего жизненного цикла приложения. Создание экземпляра класса HttpClient
для каждого запроса будет исчерпать количество сокетов, доступных при тяжелых нагрузках. Эта проблема приведет к ошибкам SocketException
. Возможные подходы к решению этой проблемы основаны на создании объекта HttpClient
в виде одноэлементного или статического, как описано в этой статье Майкрософт по использованию HttpClient. Это может быть хорошим решением для кратковременных консольных приложений или аналогичных, которые выполняются несколько раз в день.
Другая проблема, с которой сталкиваются разработчики, заключается в использовании общей версии HttpClient
в длительных процессах. В ситуации, когда HttpClient создается в виде одиночного объекта или статического объекта, он не может обрабатывать изменения DNS, как описано в этой проблеме репозитория dotnet/runtime GitHub.
Однако проблема на самом деле не связана с HttpClient
на самом деле, но с конструктором по умолчанию для HttpClient, так как он создает новый конкретный экземпляр HttpMessageHandler, который имеет сокеты исчерпания и изменения DNS, упомянутые выше.
Для решения указанных выше проблем и чтобы сделать управление экземплярами HttpClient
более простым, в .NET Core 2.1 было введено два подхода, один из которых — IHttpClientFactory. Это интерфейс, используемый для настройки и создания экземпляров HttpClient
в приложении с помощью внедрения зависимостей (DI). Он также предоставляет расширения для промежуточного слоя на основе Polly, чтобы воспользоваться делегирующими обработчиками в HttpClient.
Альтернативой является использование SocketsHttpHandler
с настроенным PooledConnectionLifetime
. Этот подход применяется к долговечным, static
или одноэлементным экземплярам HttpClient
. Дополнительные сведения о различных стратегиях см. в руководстве по HttpClient для .NET.
Polly — это библиотека для обработки временных ошибок, которая помогает разработчикам добавлять устойчивость к приложениям, используя некоторые предварительно определенные политики в простом и потокобезопасном режиме.
Преимущества использования IHttpClientFactory
Текущая реализация IHttpClientFactory, которая также реализует IHttpMessageHandlerFactory, предлагает следующие преимущества:
- Предоставляет центральное расположение для именования и настройки логических
HttpClient
объектов. Например, можно настроить клиент (агент службы), предварительно настроенный для доступа к определенной микрослужбе. - Задайте концепцию исходящего посрединного программного обеспечения через делегирование обработчиков в
HttpClient
и реализацию посрединного ПО на основе библиотеки Polly, чтобы воспользоваться её политиками для повышения устойчивости. -
HttpClient
уже имеет концепцию делегации обработчиков, которые могут быть связаны друг с другом для исходящих HTTP-запросов. Вы можете зарегистрировать HTTP-клиенты в фабрике и использовать обработчик Polly для применения политик, таких как повторные попытки и авторазрывы, и т. д. - Управляйте временем существования HttpMessageHandler, чтобы избежать упомянутых проблем, которые могут возникать при самостоятельном управлении временем существования
HttpClient
.
Совет
Экземпляры HttpClient
, внедренные с помощью DI, можно безопасно удалять, поскольку связанный HttpMessageHandler
управляется фабрикой. Внедренные HttpClient
экземпляры временные с точки зрения DI, а HttpMessageHandler
экземпляры можно рассматривать как области.
HttpMessageHandler
экземпляры имеют собственные области DI, отделяют от областей приложения (например, областей входящих запросов ASP.NET). Дополнительные сведения см. в разделе Использование HttpClientFactory в .NET.
Заметка
Реализация IHttpClientFactory
(DefaultHttpClientFactory
) тесно связана с реализацией DI в пакете NuGet Microsoft.Extensions.DependencyInjection
. Если вам нужно использовать HttpClient
без DI или с другими реализациями DI, подумайте о том, чтобы использовать static
или одиночный HttpClient
с настройкой PooledConnectionLifetime
. Дополнительные сведения см. в руководствах по HttpClient для .NET.
Несколько способов использования IHttpClientFactory
В приложении можно использовать несколько способов использования IHttpClientFactory
.
- Базовое использование
- Использование именованных клиентов
- Использование типизированных клиентов
- Использование созданных клиентов
Для краткости в этом руководстве показан наиболее структурированный способ использования IHttpClientFactory
, а именно использование типизированных клиентов (шаблон агента службы). Однако все параметры описаны и в настоящее время перечислены в этой статье , в которой рассматривается IHttpClientFactory
использования.
Заметка
Если приложению требуются файлы cookie, лучше избежать использования IHttpClientFactory в приложении. Чтобы узнать о других способах управления клиентами, смотрите руководство по использованию HTTP-клиентов.
Использование типизированных клиентов с IHttpClientFactory
Итак, что такое типизированный клиент? Это просто HttpClient
, который предварительно настроен для определенного использования. Эта конфигурация может включать определенные значения, такие как базовый сервер, заголовки HTTP или время ожидания.
На следующей схеме показано, как типизированные клиенты используются с IHttpClientFactory
:
Рис. 8-4. Использование IHttpClientFactory
с типизированными клиентскими классами.
На приведенном выше рисунке ClientService
(используемый контроллером или клиентским кодом) использует HttpClient
, созданный зарегистрированным объектом IHttpClientFactory
. Эта фабрика назначает HttpMessageHandler
из пула в HttpClient
.
HttpClient
можно настроить с помощью политик Polly при регистрации IHttpClientFactory
в контейнере DI, используя метод расширения AddHttpClient.
Чтобы настроить приведенную выше структуру, добавьте IHttpClientFactory в приложение, установив пакет NuGet Microsoft.Extensions.Http
, включающий метод расширения AddHttpClient для IServiceCollection. Этот метод расширения регистрирует внутренний класс DefaultHttpClientFactory
для использования в качестве одиночки для интерфейса IHttpClientFactory
. Он задаёт временную конфигурацию для HttpMessageHandlerBuilder. Этот обработчик сообщений (объектHttpMessageHandler), взятый из пула, используется HttpClient
, возвращенной из фабрики.
В следующем фрагменте кода вы увидите, как AddHttpClient()
можно использовать для регистрации типизированных клиентов (агентов служб), которые должны использовать 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>();
Регистрация клиентских сервисов, как показано в предыдущем фрагменте кода, позволяет DefaultClientFactory
создавать стандартную HttpClient
для каждого сервиса. Типизированный клиент регистрируется как временный в контейнере DI. В приведенном выше коде AddHttpClient()
регистрирует CatalogService, BasketService, OrderingService как временные службы, чтобы их можно было внедрить и использовать напрямую без каких-либо дополнительных регистраций.
Вы также можете добавить конфигурацию для конкретного экземпляра в регистрацию, например настроить базовый адрес и добавить некоторые политики устойчивости, как показано в следующем примере:
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
В следующем примере можно увидеть конфигурацию одной из указанных выше политик:
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
В следующей статье вы найдёте дополнительные сведения об использовании Polly.
Время существования HttpClient
Каждый раз при получении объекта HttpClient
из IHttpClientFactory
возвращается новый экземпляр. Но каждый HttpClient
использует HttpMessageHandler
, взятый из пула и повторно используемый IHttpClientFactory
для уменьшения потребления ресурсов, пока срок службы HttpMessageHandler
еще не истек.
Пул обработчиков является желательным, так как каждый обработчик обычно управляет собственными базовыми HTTP-подключениями; создание дополнительных обработчиков, чем необходимо, может привести к задержкам подключения. Некоторые обработчики также сохраняют подключения на неопределенный срок, что может предотвратить реагирование обработчика на изменения DNS.
Объекты HttpMessageHandler
в пуле имеют срок существования, равный времени, в течение которого экземпляр HttpMessageHandler
в пуле может быть повторно использован. Значение по умолчанию — две минуты, но его можно изменить для каждого типизированного клиента. Чтобы переопределить его, вызовите SetHandlerLifetime()
на IHttpClientBuilder, возвращаемой при создании клиента, как показано в следующем коде:
//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));
Каждый типизированный клиент может иметь собственное значение времени существования обработчика. Задайте время существования InfiniteTimeSpan
, чтобы отключить истечение срока действия обработчика.
Реализуйте типизированные клиентские классы, использующие внедренные и настроенные HttpClient
На предыдущем шаге необходимо определить классы типизированного клиента, такие как классы в примере кода, такие как "BasketService", "CatalogService", "OrderingService" и т. д. — Типизированный клиент является классом, принимающим объект HttpClient
(внедренный через его конструктор) и использует его для вызова какой-то удаленной службы HTTP. Например:
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;
}
}
Типизированный клиент (CatalogService
в примере) активируется di (внедрение зависимостей), что означает, что он может принимать любую зарегистрированную службу в конструкторе, помимо HttpClient
.
Типизированный клиент фактически является временным объектом, что означает, что новый экземпляр создается каждый раз, когда требуется. Он получает новый экземпляр HttpClient
каждый раз, когда он создается. Однако объекты HttpMessageHandler
в пуле — это объекты, которые повторно используются несколькими экземплярами HttpClient
.
Использование типизированных клиентских классов
Наконец, после реализации типизированных классов их можно зарегистрировать и настроить с помощью AddHttpClient()
. После этого вы можете использовать их везде, где DI внедряет службы, например, в коде страницы Razor или в контроллере веб-приложения MVC, как показано в приведенном ниже коде из 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
}
}
}
До этого момента в приведенном выше фрагменте кода показан только пример выполнения регулярных HTTP-запросов. Но "магия" представлена в следующих разделах, где показано, как все HTTP-запросы, сделанные HttpClient
, могут иметь устойчивые политики, такие как повторные попытки с экспоненциальной задержкой, аварийные выключатели, функции безопасности с помощью маркеров проверки подлинности, или даже любой другой настраиваемой функции. И все это можно сделать только путем добавления политик и делегирования обработчиков зарегистрированным типизированным клиентам.
Дополнительные ресурсы
Руководство по HttpClient для .NEThttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
Использование HttpClientFactory в .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
Использование HttpClientFactory в ASP.NET Corehttps://learn.microsoft.com/aspnet/core/fundamentals/http-requests
исходный код HttpClientFactory в репозитории
dotnet/runtime
GitHubhttps://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (библиотека устойчивости .NET и временной обработки ошибок)https://thepollyproject.azurewebsites.net/