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

Совет

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

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

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 , внедренные di, можно безопасно удалить, так как связанный HttpMessageHandler управляется фабрикой. Внедренные HttpClient экземпляры являются временными с точки зрения di, а HttpMessageHandler экземпляры можно рассматривать как область действия. HttpMessageHandlerэкземпляры имеют собственные область DI, отдельные от область приложения (например, ASP.NET входящих запросов область). Дополнительные сведения см. в статье Об использовании HttpClientFactory в .NET.

Примечание.

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

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

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

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

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

Примечание.

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

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

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

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

Diagram showing how typed clients are used with 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 для каждой службы. Типизированный клиент регистрируется как временный в контейнере 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 с помощью устойчивых политик, таких как повторные попытки с экспоненциальным обратным отключением, средствами разбиения цепи, функциями безопасности с помощью маркеров проверки подлинности или даже любой другой пользовательской функции. Все это можно сделать, добавив политики и делегировав обработчики зарегистрированным типизированным клиентам.

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