Использование IHttpClientFactory для реализации устойчивых HTTP-запросов

Совет

Это фрагмент из электронной книги Архитектура микрослужб .NET для контейнерных приложений .NET, доступной в документации .NET или в виде бесплатного pdf-файла, который можно читать в автономном режиме.

Эскиз обложки электронной книги архитектура микрослужб .NET для контейнерных приложений .NET.

IHttpClientFactory — это контракт, который реализуется DefaultHttpClientFactory и доступен, начиная с версии .NET Core 2.1. С его помощью можно создавать экземпляры HttpClient, которые используются в приложениях.

Проблемы с исходным классом HttpClient, доступным в .NET

Исходный и хорошо известный класс HttpClient очень просто использовать, но иногда разработчики применяют его неправильно.

Этот класс реализует IDisposable, однако объявлять и создавать его экземпляры в инструкции using не рекомендуется, поскольку при удалении объекта HttpClient не происходит немедленное освобождение базового сокета, в результате чего со временем может возникнуть проблема нехватки сокетов. Дополнительные сведения об этой проблеме см. в записи блога, посвященной неправильному использованию HttpClient и нарушению стабильной работы программного обеспечения.

Таким образом, создается один экземпляр HttpClient, которые будет использоваться повторно на протяжении всего жизненного цикла приложения. Создание экземпляра класса HttpClient для каждого запроса будет сокращать количество доступных сокетов при больших нагрузках. В результате будут возникать ошибки SocketException. Возможные способы решения этой проблемы основаны на создании объекта HttpClient в виде класса-одиночки или статического класса, как описано в этой статье Майкрософт об использовании HttpClient. Это может быть хорошим решением для консольных приложений, которые выполняются непродолжительное время несколько раз в день, а также их аналогов.

Кроме того, разработчики сталкиваются с проблемами при использовании общего экземпляра HttpClient в длительно выполняющихся процессах. Если экземпляр HttpClient создается в единичном виде или как статический объект, он не может обрабатывать изменения DNS, как описывается в этой проблеме в репозитории GitHub dotnet/среды выполнения.

Тем не менее эта проблема связана не с самим объектом 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 для использования политик устойчивости Polly.
  • В HttpClient уже существует концепция делегирования обработчиков, которые можно связать друг с другом для исходящих HTTP-запросов. Вы можете регистрировать клиенты HTTP в фабрике, а также использовать обработчик Polly, чтобы использовать политики Polly для повторных попыток, размыкателя цепи и т. д.
  • Управление временем существования HttpMessageHandler, чтобы избежать упомянутых проблем, которые могут возникнуть при управлении временем существования HttpClient самостоятельно.

Совет

Экземпляры HttpClient , внедренные с помощью внедрения внедрения зависимостей, можно безопасно удалить, так как связанным объектом HttpMessageHandler управляет фабрика. Внедренные HttpClient экземпляры являются временными с точки зрения внедрения внедрения, в то время как HttpMessageHandler экземпляры могут рассматриваться как ограниченные. HttpMessageHandler Экземпляры имеют собственные области внедрения зависимостей, отдельные от областей приложения (например, ASP.NET области входящих запросов). Дополнительные сведения см. в статье Использование HttpClientFactory в .NET.

Примечание

Реализация IHttpClientFactory (DefaultHttpClientFactory) тесно привязывается к реализации внедрения зависимостей в пакете NuGet Microsoft.Extensions.DependencyInjection. Если необходимо использовать HttpClient без внедрения внедрения зависимостей или с другими реализациями внедрения внедрения зависимостей, рассмотрите static возможность использования или singleton HttpClient с PooledConnectionLifetime настройкой. Дополнительные сведения см. в статье Рекомендации по HttpClient для .NET.

Способы применения IHttpClientFactory

Есть несколько способов использования IHttpClientFactory в вашем приложении:

  • Основное использование
  • Использование именованных клиентов
  • Использование типизированных клиентов
  • Использование созданных клиентов

Для краткости в этом руководстве показан наиболее структурированный способ использования IHttpClientFactory, а именно — с помощью типизированных клиентов (шаблон агента службы). Все параметры описаны и перечислены в этой статье, посвященной использованию IHttpClientFactory.

Примечание

Если вашему приложению требуются файлы cookie, рекомендуется избегать использования IHttpClientFactory в приложении. Альтернативные способы управления клиентами см. в статье Рекомендации по использованию HTTP-клиентов.

Как использовать типизированные клиенты с IHttpClientFactory

Так что же такое типизированный клиент? Это просто объект HttpClient, предварительно настроенный для конкретной цели. Эта конфигурация может включать заданные значения, например базовый сервер, заголовки HTTP или величины времени ожидания.

На следующей схеме показано, как использовать типизированные клиенты с IHttpClientFactory.

На схеме показано, как использовать типизированные клиенты с IHttpClientFactory.

Рис. 8-4. Использование IHttpClientFactory с классами типизированных клиентов.

На изображении выше ClientService (используется контроллером или в коде клиента) использует объект HttpClient, созданный зарегистрированной фабрикой IHttpClientFactory. Эта фабрика назначает HttpMessageHandler из пула объекту HttpClient. HttpClient можно настроить с помощью политик Polly при регистрации фабрики IHttpClientFactory в контейнере внедрения зависимостей, используя метод расширения 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 для каждой службы. Типизированный клиент регистрируется как временный в контейнере внедрения зависимостей. В приведенном выше коде регистрирует CatalogService, BasketService, OrderingService как временные службы, AddHttpClient() чтобы их можно было внедрить и использовать напрямую без дополнительной регистрации.

Вы также можете добавить конфигурацию для конкретного экземпляра в регистрацию, например настроить базовый адрес и добавить некоторые политики устойчивости, как показано ниже.

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 в примере) активируется путем внедрения зависимостей( внедрения зависимостей), что означает, что он может принимать любую зарегистрированную службу в своем конструкторе в дополнение к HttpClient.

Типизированный клиент фактически является временным объектом, что означает, что новый экземпляр создается каждый раз, когда требуется один. Он получает новый экземпляр HttpClient каждый раз, когда он создается. Тем не менее объекты HttpMessageHandler в пуле используются повторно множеством экземпляров HttpClient.

Использование классов типизированных клиентов

Наконец, когда вы реализуете классы типов, а также зарегистрируете и настроите их в AddHttpClient(). После этого их можно использовать везде, где службы внедряются с помощью внедрения внедрения, например в коде страницы 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 , могут иметь устойчивые политики, такие как повторные попытки с экспоненциальной задержкой, автоматические выключатели, функции безопасности с использованием маркеров проверки подлинности или даже любые другие пользовательские функции. Все это можно сделать, добавив политики и делегировав обработчики зарегистрированным типизированным клиентам.

Дополнительные ресурсы