HttpClient и IHttpClientFactory, HeaderPropagation
↑ 3.1 Хост, конфигурация и middleware pipeline · ← Предыдущая
Зачем это на собесе
Классическая ошибка
new HttpClient()на каждый запрос и знание Polly/resilience — признак опыта.
Объяснение
Два противоположных подводных камня:
- Создавать
HttpClientна каждый запрос — исчерпание сокетов (SocketException, TIME_WAIT). - Один статический
HttpClientнавсегда — DNS кэшируется, изменения адреса не подхватываются.
IHttpClientFactory решает оба: управляет пулом HttpMessageHandler и периодически пересоздаёт их.
builder.Services.AddHttpClient<IPaymentsClient, PaymentsClient>(c =>
{
c.BaseAddress = new Uri("https://payments.internal/");
c.Timeout = TimeSpan.FromSeconds(10);
})
.AddStandardResilienceHandler(); // retry, circuit breaker, timeout (Microsoft.Extensions.Http.Resilience)
public class PaymentsClient(HttpClient http) : IPaymentsClient
{
public async Task<PaymentDto?> GetAsync(Guid id, CancellationToken ct)
=> await http.GetFromJsonAsync<PaymentDto>($"payments/{id}", ct);
}| Способ | Когда |
|---|---|
| Typed client | клиент к конкретному сервису с методами |
| Named client | несколько настроек одним типом |
CreateClient() | простые случаи |
Проброс заголовков
builder.Services.AddHeaderPropagation(o => { o.Headers.Add("X-Correlation-Id"); o.Headers.Add("Authorization"); });
builder.Services.AddHttpClient<IPaymentsClient, PaymentsClient>().AddHeaderPropagation();
app.UseHeaderPropagation();Так входящие заголовки автоматически копируются в исходящие запросы к другим сервисам.
Устойчивость
Retry с экспоненциальной паузой и jitter, circuit breaker, timeout на попытку и общий. Повторять можно только идемпотентные запросы.
Нюансы и подводные камни
- Всегда передавайте
CancellationToken. - Проверяйте
EnsureSuccessStatusCode()или статус: 4xx/5xx не бросают исключение сами. - Читайте тело потоком, а
HttpResponseMessageосвобождайте (using). Timeoutклиента не заменяет таймаут на попытку в resilience handler.- Не логируйте заголовок
Authorization.
Практика
- Настройте typed client с retry и circuit breaker и сымитируйте падение сервиса.
- Пробросьте
X-Correlation-Idмежду двумя своими сервисами. - Воспроизведите исчерпание сокетов в цикле с
new HttpClient().
Вопросы с ответами
Почему нельзя создавать HttpClient на каждый запрос?
Каждое соединение остаётся в TIME_WAIT, сокеты заканчиваются под нагрузкой; нужен повторно используемый handler.
Зачем IHttpClientFactory?
Управляет временем жизни handler-ов (решает проблему DNS), даёт типизированные клиенты и подключение resilience и логирования.
Что можно ретраить?
Идемпотентные операции и временные ошибки (5xx, таймауты, 429); POST без ключа идемпотентности — только осторожно.