Поделиться через


Использование 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, как описано в этой проблеме репозитория 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:

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

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