HTTP клиент на C#: продвинутые техники работы с запросами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

HTTP клиент на C#: делаем запросы правильно — продвинутый уровень

В современной .NET-разработке работа с HTTP-запросами выходит далеко за рамки простого вызова WebClient или базового HttpClient. На продвинутом уровне необходимо учитывать управление пулом соединений, корректную обработку ошибок, таймауты, политики повторных попыток и асинхронное выполнение. В этой статье мы разберём лучшие практики работы с HTTP-клиентом на C#, которые помогут вам создавать надёжные и производительные приложения.

1. Правильная работа с HttpClient и пулом соединений

Одна из самых частых ошибок — создание нового экземпляра HttpClient на каждый запрос. Это приводит к истощению сокетов (socket exhaustion) из-за того, что каждый экземпляр держит открытое TCP-соединение в состоянии TIME_WAIT.

Неправильно:

public async Task<string> GetDataAsync(string url)
{
    using (var client = new HttpClient())
    {
        return await client.GetStringAsync(url);
    }
}

Правильно: используйте один статический или синглтон-экземпляр HttpClient.

public class HttpService
{
    private static readonly HttpClient _client = new HttpClient();

    public async Task<string> GetDataAsync(string url)
    {
        return await _client.GetStringAsync(url);
    }
}

Для более тонкого контроля используйте IHttpClientFactory из пакета Microsoft.Extensions.Http. Фабрика управляет пулом и автоматически восстанавливает соединения при сбоях DNS.

// В Startup.cs или Program.cs
services.AddHttpClient("MyClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
});

// В сервисе
public class MyService
{
    private readonly IHttpClientFactory _httpClientFactory;

    public MyService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<string> GetDataAsync()
    {
        var client = _httpClientFactory.CreateClient("MyClient");
        return await client.GetStringAsync("/data");
    }
}

2. Таймауты и отмена запросов

Без таймаутов ваш клиент может зависнуть на неопределённое время. В HttpClient есть два механизма:

  • Timeout — общий таймаут для всего запроса.
  • CancellationToken — для отмены запроса извне (например, пользователь закрыл приложение).

Устанавливайте Timeout разумно. Для большинства API подойдёт 10–30 секунд.

var client = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(15)
};

// Пример с CancellationTokenSource
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
    var response = await client.GetAsync("https://slow-api.example.com", cts.Token);
}
catch (TaskCanceledException)
{
    Console.WriteLine("Запрос отменён по таймауту.");
}

Важно: Timeout и CancellationToken работают совместно. Если CancellationToken срабатывает раньше — запрос отменяется быстрее.

3. Обработка ошибок и политика повторных попыток (Retry)

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

using Polly;
using Polly.Extensions.Http;

// Определяем политику повторных попыток
var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError() // 5xx, 408
    .Or<HttpRequestException>()
    .WaitAndRetryAsync(3, retryAttempt =>
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

// Применяем к клиенту
services.AddHttpClient("RetryClient")
    .AddPolicyHandler(retryPolicy);

Также полезно добавить политику Circuit Breaker (автоматическое отключение вызовов при высокой частоте ошибок):

var circuitBreakerPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

services.AddHttpClient("ResilientClient")
    .AddPolicyHandler(retryPolicy)
    .AddPolicyHandler(circuitBreakerPolicy);

Не забывайте логировать ошибки. Используйте ILogger для записи деталей сбоя.

4. Продвинутая работа с заголовками и контентом

На продвинутом уровне часто требуется кастомная сериализация, работа с куками, аутентификация (Bearer, Basic) и потоковая передача данных.

Пример с Bearer-аутентификацией:

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "your_token_here");

// Или динамически для каждого запроса
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/secure");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);

Потоковая передача больших данных:

using var response = await client.GetAsync("https://example.com/large-file",
    HttpCompletionOption.ResponseHeadersRead); // не буферизировать сразу
using var stream = await response.Content.ReadAsStreamAsync();
// Читаем поток частями
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
    // Обрабатываем chunk
}

Работа с куками:

var cookieContainer = new CookieContainer();
var handler = new HttpClientHandler
{
    CookieContainer = cookieContainer,
    UseCookies = true
};
var client = new HttpClient(handler);

// После запроса куки автоматически сохранятся
await client.GetAsync("https://example.com/login");
var cookies = cookieContainer.GetCookies(new Uri("https://example.com"));

Заключение

Правильная работа с HTTP-клиентом на C# — это не просто вызов метода GetAsync. Используйте IHttpClientFactory для управления пулом, настраивайте таймауты и отмену, внедряйте политики повторных попыток с помощью Polly, и не забывайте про поток

Блоги

Рекомендаций по книгам