Как создать HTTP клиент в C#
Что проверяют
Знаете ли вы, почему HttpClient нельзя создавать на каждый запрос и почему один статический экземпляр тоже не идеален. Следом спрашивают про IHttpClientFactory, таймауты и повторы.
Как отвечать
Сам вызов простой: GetAsync, PostAsJsonAsync, GetFromJsonAsync из System.Net.Http.Json, затем EnsureSuccessStatusCode() или проверка IsSuccessStatusCode. Интересна часть про жизненный цикл:
- new на каждый запрос: каждый экземпляр со своим обработчиком открывает свои соединения, после
Disposeсокеты висят вTIME_WAIT, и под нагрузкой заканчиваются порты; - один статический клиент: соединения переиспользуются, но пул не видит смены DNS. Лечится через
SocketsHttpHandlerсPooledConnectionLifetime, например в несколько минут; IHttpClientFactory: в ASP.NET Core клиент регистрируется черезservices.AddHttpClient<TClient>(), фабрика держит пул обработчиков и пересоздаёт их (по умолчанию раз в две минуты). Типизированный клиент получает настроенныйHttpClientчерез конструктор;- устойчивость: пакет
Microsoft.Extensions.Http.ResilienceиAddStandardResilienceHandler()дают повторы, circuit breaker и таймауты на основе Polly.
Добавьте детали, которые выдают практику. Timeout по умолчанию 100 секунд, для внутренних сервисов это слишком много. В каждый вызов передаётся CancellationToken. BaseAddress и общие заголовки задаются при регистрации, а не перед каждым запросом. Для больших ответов используется HttpCompletionOption.ResponseHeadersRead и чтение потоком, чтобы не буферизовать тело целиком.
Где спотыкаются
Оборачивают клиент в using внутри метода. Код выглядит аккуратно, но под нагрузкой именно он приводит к исчерпанию сокетов.
Внедряют типизированный клиент в singleton-сервис. Тогда обработчик никогда не обновится, и выгода фабрики пропадает вместе с реакцией на смену DNS.
Меняют DefaultRequestHeaders у общего экземпляра перед запросом. Такая правка не потокобезопасна, параллельные запросы получат чужие заголовки. Заголовки конкретного запроса ставятся в HttpRequestMessage.