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


Создание HTTP-запросов с помощью класса HttpClient

В этой статье вы узнаете, как выполнять HTTP-запросы и обрабатывать ответы с помощью класса HttpClient.

Внимание

Все примеры HTTP-запросов в этой статье предназначены для одного из следующих URL-адресов:

  • https://jsonplaceholder.typicode.com: сайт, предоставляющий бесплатную фиктивную платформу API для тестирования и прототипирования.
  • https://www.example.com: домен, доступный для использования в иллюстрирующих примерах в документах.

Конечные точки HTTP обычно возвращают данные нотации объектов JavaScript (JSON), но не всегда. Для удобства необязательный пакет NuGet System.Net.Http. Json предоставляет несколько методов расширения для объектов HttpClient и HttpContent, которые выполняют автоматическую сериализацию и десериализацию с помощью пакета NuGet 📦 System.Text.Json. Примеры, приведенные в этой статье, относятся к местам, где доступны эти расширения.

Совет

Весь исходный код, на который ссылается в этой статье, доступен в репозитории GitHub: документация .NET.

Создание объекта HttpClient

Большинство примеров в этой статье повторно используют один и тот же экземпляр HttpClient, чтобы можно было настроить экземпляр один раз и использовать его для оставшихся примеров. Чтобы создать объект HttpClient, используйте конструктор класса HttpClient. Дополнительные сведения см. в руководствах по использованию HttpClient.

// HttpClient lifecycle management best practices:
// https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use
private static HttpClient sharedClient = new()
{
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com"),
};

Код выполняет следующие задачи:

  • Создайте новый экземпляр HttpClient и определите его как переменную static. В соответствии с рекомендациями рекомендуется повторно использовать экземпляры HttpClient во время жизненного цикла приложения.
  • Задайте для свойства HttpClient.BaseAddress значение "https://jsonplaceholder.typicode.com".

Этот HttpClient экземпляр использует базовый адрес для последующих запросов. Чтобы применить другие конфигурации, рассмотрите следующие API:

Совет

Кроме того, можно создать экземпляры HttpClient с помощью шаблона проектирования "фабрика", который позволяет настроить любое количество клиентов и использовать их в качестве служб внедрения зависимости. Дополнительные сведения см. в разделе "Фабрика клиентов HTTP" с помощью .NET.

Создание HTTP-запроса

Чтобы выполнить HTTP-запрос, вызовите любой из следующих методов API:

Метод HTTP API
GET HttpClient.GetAsync
GET HttpClient.GetByteArrayAsync
GET HttpClient.GetStreamAsync
GET HttpClient.GetStringAsync
POST HttpClient.PostAsync
PUT HttpClient.PutAsync
PATCH HttpClient.PatchAsync
DELETE HttpClient.DeleteAsync
USER SPECIFIED HttpClient.SendAsync

запрос USER SPECIFIED указывает, что метод SendAsync принимает любой допустимый объект HttpMethod.

Предупреждение

Выполнение HTTP-запросов считается работой с привязкой к сети ввода-вывода. Синхронный метод HttpClient.Send существует, но рекомендация заключается в том, чтобы использовать асинхронные API вместо этого, если у вас нет веской причины.

Примечание.

Прицеливаясь на устройства Android (например, при разработке .NET MAUI), необходимо добавить определение android:usesCleartextTraffic="true" в раздел <application></application> в файле AndroidManifest.xml. Этот параметр включает чистый текстовый трафик, например HTTP-запросы, которые по умолчанию отключены из-за политик безопасности Android. Рассмотрим следующие примеры параметров XML:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application android:usesCleartextTraffic="true"></application>
  <!-- omitted for brevity -->
</manifest>

Дополнительные сведения см. в разделе Включение текстового сетевого трафика для домена localhost.

Общие сведения о содержимом HTTP

Тип HttpContent используется для представления текста сущности HTTP и соответствующих заголовков содержимого. Для методов HTTP (или методов запроса), для которых требуется текст (POST, PUT, PATCH), используется класс HttpContent для указания текста запроса. В большинстве примеров показано, как подготовить StringContent подкласс с JSON-данными, но другие подклассы существуют для различных типов содержимого (MIME).

  • ByteArrayContent: предоставляет содержимое HTTP на основе массива байтов.
  • FormUrlEncodedContent: предоставляет http-содержимое для кортежей имен и значений, закодированных с помощью типа MIME "application/x-www-form-urlencoded".
  • JsonContent: предоставляет содержимое HTTP на основе JSON.
  • MultipartContent: предоставляет коллекцию объектов HttpContent, сериализованных с помощью спецификации типа MIME "multipart/*".
  • MultipartFormDataContent: предоставляет контейнер для содержимого, закодированного с помощью типа MIME "multipart/form-data".
  • ReadOnlyMemoryContent: обеспечивает HTTP-содержимое на основе значения ReadOnlyMemory<T>.
  • StreamContent: предоставляет HTTP-содержимое, используя поток.
  • StringContent: предоставляет содержимое HTTP на основе строки.

Класс HttpContent также используется для представления текста отклика класса HttpResponseMessage, который доступен в свойстве HttpResponseMessage.Content.

Использование HTTP-запроса GET

Запрос GET не должен отправлять текст. Этот запрос используется (как указывает имя метода) для получения (или получения) данных из ресурса. Чтобы выполнить запрос HTTP GET с использованием экземпляра HttpClient и объекта Uri, используйте метод HttpClient.GetAsync:

static async Task GetAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.GetAsync("todos/3");
    
    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 3,
    //     "title": "fugiat veniam minus",
    //     "completed": false
    //   }
}

Код выполняет следующие задачи:

  • Выполните запрос GET к конечной точке "https://jsonplaceholder.typicode.com/todos/3".
  • Убедитесь, что ответ выполнен успешно.
  • Напишите сведения о запросе в консоль.
  • Прочтите тело ответа как строку.
  • Напишите текст ответа JSON в консоль.

Метод WriteRequestToConsole — это пользовательское расширение, которое не является частью платформы. Если вы хотите узнать о реализации, рассмотрите следующий код C#:

static class HttpResponseMessageExtensions
{
    internal static void WriteRequestToConsole(this HttpResponseMessage response)
    {
        if (response is null)
        {
            return;
        }

        var request = response.RequestMessage;
        Console.Write($"{request?.Method} ");
        Console.Write($"{request?.RequestUri} ");
        Console.WriteLine($"HTTP/{request?.Version}");        
    }
}

Эта функция используется для записи сведений о запросе в консоль в следующей форме:

<HTTP Request Method> <Request URI> <HTTP/Version>

Например, запрос GET к конечной точке "https://jsonplaceholder.typicode.com/todos/3" выводит следующее сообщение:

GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1

Создание HTTP-запроса GET из JSON

Конечная точка https://jsonplaceholder.typicode.com/todos возвращает массив JSON объектов Todo. Их структура JSON похожа на следующую форму:

[
  {
    "userId": 1,
    "id": 1,
    "title": "example title",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "another example title",
    "completed": true
  },
]

Объект C# Todo определяется следующим образом:

public record class Todo(
    int? UserId = null,
    int? Id = null,
    string? Title = null,
    bool? Completed = null);

record class Это тип, с необязательнымиId, TitleCompletedи UserId свойствами. Дополнительные сведения о типе record см. в разделе "Общие сведения о типах записей в C#". Чтобы автоматически десериализовать запросы GET в строго типизированный объект C#, используйте метод расширения GetFromJsonAsync, который является частью пакета NuGet System.Net.Http.Json 📦.

static async Task GetFromJsonAsync(HttpClient httpClient)
{
    var todos = await httpClient.GetFromJsonAsync<List<Todo>>(
        "todos?userId=1&completed=false");

    Console.WriteLine("GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1");
    todos?.ForEach(Console.WriteLine);
    Console.WriteLine();

    // Expected output:
    //   GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1
    //   Todo { UserId = 1, Id = 1, Title = delectus aut autem, Completed = False }
    //   Todo { UserId = 1, Id = 2, Title = quis ut nam facilis et officia qui, Completed = False }
    //   Todo { UserId = 1, Id = 3, Title = fugiat veniam minus, Completed = False }
    //   Todo { UserId = 1, Id = 5, Title = laboriosam mollitia et enim quasi adipisci quia provident illum, Completed = False }
    //   Todo { UserId = 1, Id = 6, Title = qui ullam ratione quibusdam voluptatem quia omnis, Completed = False }
    //   Todo { UserId = 1, Id = 7, Title = illo expedita consequatur quia in, Completed = False }
    //   Todo { UserId = 1, Id = 9, Title = molestiae perspiciatis ipsa, Completed = False }
    //   Todo { UserId = 1, Id = 13, Title = et doloremque nulla, Completed = False }
    //   Todo { UserId = 1, Id = 18, Title = dolorum est consequatur ea mollitia in culpa, Completed = False }
}

Код выполняет следующие задачи:

  • Сделайте запрос GET в "https://jsonplaceholder.typicode.com/todos?userId=1&completed=false".

    Строка запроса представляет критерии фильтрации для запроса. При успешном выполнении команды ответ автоматически десериализируется в объект List<Todo>.

  • Напишите сведения о запросе в консоль вместе с каждым объектом Todo.

Использование HTTP-запроса POST

Запрос POST отправляет данные на сервер для обработки. Заголовок Content-Type запроса указывает, какой MIME тип отправляет тело сообщения. Чтобы выполнить запрос HTTP POST с использованием экземпляра HttpClient и объекта Uri, используйте метод HttpClient.PostAsync:

static async Task PostAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new
        {
            userId = 77,
            id = 1,
            title = "write code sample",
            completed = false
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PostAsync(
        "todos",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
    //   {
    //     "userId": 77,
    //     "id": 201,
    //     "title": "write code sample",
    //     "completed": false
    //   }
}

Код выполняет следующие задачи:

  • Подготовьте экземпляр StringContent с текстом JSON запроса (тип MIME "application/json").
  • Выполните запрос POST к конечной точке "https://jsonplaceholder.typicode.com/todos".
  • Убедитесь, что ответ был успешным, и запишите сведения о запросе в консоль.
  • Напишите текст ответа в виде строки в консоль.

Создание HTTP-запроса POST в формате JSON

Чтобы автоматически сериализовать аргументы запроса и десериализовать ответы в строго типизированные POST объекты C#, используйте PostAsJsonAsync метод расширения, который входит в пакет NuGet System.Net.Http.Json .

static async Task PostAsJsonAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.PostAsJsonAsync(
        "todos", 
        new Todo(UserId: 9, Id: 99, Title: "Show extensions", Completed: false));

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var todo = await response.Content.ReadFromJsonAsync<Todo>();
    Console.WriteLine($"{todo}\n");

    // Expected output:
    //   POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
    //   Todo { UserId = 9, Id = 201, Title = Show extensions, Completed = False }
}

Код выполняет следующие задачи:

  • Сериализируйте экземпляр Todo в формате JSON и выполните запрос POST к конечной точке "https://jsonplaceholder.typicode.com/todos".
  • Убедитесь в успешности ответа и запишите детали запроса в консоль.
  • Десериализуйте тело ответа в экземпляр Todo и выведите объект Todo на консоль.

Использование HTTP-запроса PUT

Метод запроса PUT заменяет существующий ресурс или создает новый, используя передаваемые данные запроса. Чтобы выполнить запрос HTTP PUT с использованием экземпляра HttpClient и объекта Uri, используйте метод HttpClient.PutAsync:

static async Task PutAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new 
        {
            userId = 1,
            id = 1,
            title = "foo bar",
            completed = false
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PutAsync(
        "todos/1",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   PUT https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 1,
    //     "title": "foo bar",
    //     "completed": false
    //   }
}

Код выполняет следующие задачи:

  • Подготовьте экземпляр StringContent с текстом JSON запроса (тип MIME "application/json").
  • Выполните запрос PUT к конечной точке "https://jsonplaceholder.typicode.com/todos/1".
  • Убедитесь, что ответ выполнен успешно, и запишите детали запроса вместе с телом ответа в формате JSON в консоль.

Создание HTTP-запроса PUT в формате JSON

Чтобы автоматически сериализовать аргументы запроса и десериализовать ответы в строго типизированные PUT объекты C#, используйте PutAsJsonAsync метод расширения, который входит в пакет NuGet System.Net.Http.Json .

static async Task PutAsJsonAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.PutAsJsonAsync(
        "todos/5",
        new Todo(Title: "partially update todo", Completed: true));

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var todo = await response.Content.ReadFromJsonAsync<Todo>();
    Console.WriteLine($"{todo}\n");

    // Expected output:
    //   PUT https://jsonplaceholder.typicode.com/todos/5 HTTP/1.1
    //   Todo { UserId = , Id = 5, Title = partially update todo, Completed = True }
}

Код выполняет следующие задачи:

  • Сериализируйте экземпляр Todo в формате JSON и выполните запрос PUT к конечной точке "https://jsonplaceholder.typicode.com/todos/5".
  • Убедитесь, что ответ успешен, и запишите сведения о запросе в консоль.
  • Десериализуйте тело ответа в экземпляр Todo и запишите объекты Todo в консоль.

Использование HTTP-запроса PATCH

Запрос PATCH является частичным обновлением существующего ресурса. Этот запрос не создает новый ресурс и не предназначен для замены существующего ресурса. Вместо этого этот метод частично обновляет ресурс. Чтобы выполнить запрос HTTP PATCH с использованием экземпляра HttpClient и объекта Uri, используйте метод HttpClient.PatchAsync:

static async Task PatchAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new
        {
            completed = true
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PatchAsync(
        "todos/1",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output
    //   PATCH https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 1,
    //     "title": "delectus aut autem",
    //     "completed": true
    //   }
}

Код выполняет следующие задачи:

  • Подготовьте экземпляр StringContent с текстом JSON запроса (тип MIME "application/json").
  • Выполните запрос PATCH к конечной точке "https://jsonplaceholder.typicode.com/todos/1".
  • Убедитесь, что ответ успешен, и записывайте детали запроса с текстом ответа JSON в консоль.

Методы расширения не существуют для PATCH запросов в пакете System.Net.Http.Json NuGet.

Использование http DELETE-запроса

Запрос DELETE удаляет существующий ресурс, и запрос идемпотентен, но не безопасен. Несколько DELETE запросов к тем же ресурсам дают один и тот же результат, но запрос влияет на состояние ресурса. Чтобы выполнить запрос HTTP DELETE с использованием экземпляра HttpClient и объекта Uri, используйте метод HttpClient.DeleteAsync:

static async Task DeleteAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.DeleteAsync("todos/1");
    
    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output
    //   DELETE https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {}
}

Код выполняет следующие задачи:

  • Выполните запрос DELETE к конечной точке "https://jsonplaceholder.typicode.com/todos/1".
  • Убедитесь в успешности ответа и запишите сведения о запросе в консоль.

Совет

Ответ на запрос DELETE (как и запрос PUT) может или не включать текст.

Изучение HTTP-запроса HEAD

Запрос HEAD похож на GET запрос. Вместо возврата ресурса этот запрос возвращает только заголовки, связанные с ресурсом. Ответ на запрос HEAD не возвращает тело. Чтобы выполнить запрос HTTP HEAD с экземпляром HttpClient и объектом Uri, используйте метод HttpClient.SendAsync с типом HttpMethod, установленным на HttpMethod.Head.

static async Task HeadAsync(HttpClient httpClient)
{
    using HttpRequestMessage request = new(
        HttpMethod.Head, 
        "https://www.example.com");

    using HttpResponseMessage response = await httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    foreach (var header in response.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
    Console.WriteLine();

    // Expected output:
    //   HEAD https://www.example.com/ HTTP/1.1
    //   Accept-Ranges: bytes
    //   Age: 550374
    //   Cache-Control: max-age=604800
    //   Date: Wed, 10 Aug 2022 17:24:55 GMT
    //   ETag: "3147526947"
    //   Server: ECS, (cha / 80E2)
    //   X-Cache: HIT
}

Код выполняет следующие задачи:

  • Выполните запрос HEAD к конечной точке "https://www.example.com/".
  • Убедитесь в успешности ответа и запишите сведения о запросе в консоль.
  • Выполняет итерацию по всем заголовкам ответа и записывает каждый заголовок в консоль.

Изучение HTTP-запроса OPTIONS

Запрос OPTIONS используется для определения методов HTTP, поддерживаемых сервером или конечной точкой. Чтобы выполнить запрос HTTP OPTIONS с использованием экземпляра HttpClient и объекта Uri, примените метод HttpClient.SendAsync, установив тип HttpMethod в HttpMethod.Options.

static async Task OptionsAsync(HttpClient httpClient)
{
    using HttpRequestMessage request = new(
        HttpMethod.Options, 
        "https://www.example.com");

    using HttpResponseMessage response = await httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    foreach (var header in response.Content.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
    Console.WriteLine();

    // Expected output
    //   OPTIONS https://www.example.com/ HTTP/1.1
    //   Allow: OPTIONS, GET, HEAD, POST
    //   Content-Type: text/html; charset=utf-8
    //   Expires: Wed, 17 Aug 2022 17:28:42 GMT
    //   Content-Length: 0
}

Код выполняет следующие задачи:

  • Отправьте HTTP-запрос OPTIONS в конечную точку "https://www.example.com/".
  • Убедитесь, что ответ успешно выполнен, и запишите сведения о запросе в консоль.
  • Выполняет итерацию по всем заголовкам содержимого ответа и записывает каждый заголовок в консоль.

Изучение HTTP-запроса TRACE

Запрос TRACE может быть полезен для отладки, так как он предоставляет обратную связь на уровне приложения для сообщения запроса. Чтобы сделать запрос HTTP TRACE, создайте HttpRequestMessage с помощью типа HttpMethod.Trace:

using HttpRequestMessage request = new(
    HttpMethod.Trace, 
    "{ValidRequestUri}");

Внимание

Не все HTTP-серверы поддерживают метод TRACE HTTP. Этот метод может предоставить уязвимость безопасности, если она используется неразумно. Дополнительные сведения см. в разделе Open Web Application Security Project (OWASP): Cross Site Tracing.

Обработка HTTP-ответа

При обработке HTTP-ответа вы взаимодействуете с типом HttpResponseMessage. Несколько членов используются для оценки допустимости ответа. Код состояния HTTP доступен в свойстве HttpResponseMessage.StatusCode.

Предположим, что вы отправляете запрос, используя клиентский экземпляр.

using HttpResponseMessage response = await httpClient.SendAsync(request);

Чтобы убедиться, что response равно OK (код состояния HTTP 200), можно оценить это значение, как показано в следующем примере:

if (response is { StatusCode: HttpStatusCode.OK })
{
    // Omitted for brevity...
}

Существуют другие коды состояния HTTP, представляющие успешный ответ, например CREATED (код состояния HTTP 201), ACCEPTED (код состояния HTTP 202), NO CONTENT (код состояния HTTP 204) и RESET CONTENT (код состояния HTTP 205). Вы можете использовать свойство HttpResponseMessage.IsSuccessStatusCode для оценки этих кодов, которое гарантирует, что код состояния ответа находится в диапазоне 200–299.

if (response.IsSuccessStatusCode)
{
    // Omitted for brevity...
}

Если требуется, чтобы платформа вызвала ошибку HttpRequestException, можно вызвать метод HttpResponseMessage.EnsureSuccessStatusCode():

response.EnsureSuccessStatusCode();

Этот код вызывает ошибку HttpRequestException, если код состояния ответа не относится к диапазону 200–299.

Изучение допустимых ответов на содержимое HTTP

Имея допустимый ответ, вы можете получить доступ к тексту ответа с помощью свойства Content. Текст доступен как экземпляр HttpContent, который можно использовать для доступа к тексту в виде потока, массива байтов или строки.

Следующий код использует объект responseStream для чтения текста ответа:

await using Stream responseStream =
    await response.Content.ReadAsStreamAsync();

Для чтения текста ответа можно использовать различные объекты. Используйте объект responseByteArray для чтения текста ответа:

byte[] responseByteArray = await response.Content.ReadAsByteArrayAsync();

Используйте объект responseString для чтения текста ответа:

string responseString = await response.Content.ReadAsStringAsync();

Если вы знаете, что конечная точка HTTP возвращает JSON, вы можете десериализировать текст ответа в любой допустимый объект C# с помощью пакета NuGet System.Net.Http.Json:

T? result = await response.Content.ReadFromJsonAsync<T>();

В этом коде значение result является десериализированным телом ответа в виде типа T.

Использование обработки ошибок HTTP

При сбое HTTP-запроса система создает объект HttpRequestException. Перехват исключения может оказаться недостаточным. Существуют и другие потенциальные исключения, которые вы, возможно, захотите рассмотреть для обработки. Например, вызывающий код может использовать маркер отмены, который был отменен до завершения запроса. В этом сценарии можно зафиксировать ошибку TaskCanceledException.

using var cts = new CancellationTokenSource();
try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/sleepFor?seconds=100", cts.Token);
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
    // When the token has been canceled, it is not a timeout.
    Console.WriteLine($"Canceled: {ex.Message}");
}

Аналогичным образом, при выполнении HTTP-запроса, если сервер не отвечает до превышения значения HttpClient.Timeout, возникает то же исключение. В этом сценарии можно определить, что произошёл тайм-аут, проанализировав свойство Exception.InnerException при перехвате ошибки TaskCanceledException.

try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/sleepFor?seconds=100");
}
catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException tex)
{
    Console.WriteLine($"Timed out: {ex.Message}, {tex.Message}");
}

В коде, когда внутреннее исключение имеет тип TimeoutException, произошла задержка, и маркер отмены не отменяет запрос.

Чтобы оценить код состояния HTTP при перехвате объекта HttpRequestException, можно оценить свойство HttpRequestException.StatusCode:

try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/doesNotExist");

    response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    // Handle 404
    Console.WriteLine($"Not found: {ex.Message}");
}

В коде вызывается метод EnsureSuccessStatusCode(), чтобы вызвать исключение, если ответ не выполнен. Затем свойство HttpRequestException.StatusCode оценивается, чтобы определить, был ли ответ кодом состояния HTTP 404 (404). В объекте HttpClient есть несколько вспомогательных методов, которые неявно вызывают метод EnsureSuccessStatusCode от вашего имени.

Для передачи ошибок HTTP рассмотрим следующие API:

Совет

Все методы HttpClient, используемые для выполнения HTTP-запросов, которые не возвращают тип HttpResponseMessage неявно вызывают метод EnsureSuccessStatusCode от вашего имени.

При вызове этих методов можно обработать объект HttpRequestException и оценить свойство HttpRequestException.StatusCode, чтобы определить код состояния HTTP ответа:

try
{
    // These extension methods will throw HttpRequestException
    // with StatusCode set when the HTTP request status code isn't 2xx:
    //
    //   GetByteArrayAsync
    //   GetStreamAsync
    //   GetStringAsync

    using var stream = await httpClient.GetStreamAsync(
        "https://localhost:5001/doesNotExists");
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    // Handle 404
    Console.WriteLine($"Not found: {ex.Message}");
}

В коде могут быть ситуации, в которых необходимо выбросить объект HttpRequestException. Конструктор HttpRequestException() является общедоступным, и его можно использовать для создания исключения с пользовательским сообщением:

try
{
    using var response = await httpClient.GetAsync(
        "https://localhost:5001/doesNotExists");

    // Throw for anything higher than 400.
    if (response is { StatusCode: >= HttpStatusCode.BadRequest })
    {
        throw new HttpRequestException(
            "Something went wrong", inner: null, response.StatusCode);
    }
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    Console.WriteLine($"Not found: {ex.Message}");
}

Настройка прокси-сервера HTTP

Прокси-сервер HTTP можно настроить одним из двух способов. Значение по умолчанию указывается в свойстве HttpClient.DefaultProxy . Кроме того, можно указать прокси-сервер для HttpClientHandler.Proxy свойства.

Использование глобального прокси-сервера по умолчанию

Свойство HttpClient.DefaultProxy — это статическое свойство, определяющее прокси-сервер по умолчанию, используемый всеми экземплярами HttpClient, если прокси-сервер явно не задан в объекте HttpClientHandler, передаваемом через его конструктор.

Экземпляр по умолчанию, возвращаемый этим свойством, инициализируется в соответствии с другим набором правил в зависимости от платформы:

  • Windows: прочитать конфигурацию прокси-сервера из переменных среды, или, если переменные не определены, прочитать из параметров прокси-сервера пользователя.
  • macOS: читать конфигурацию прокси-сервера из переменных среды, или, если переменные не определены, использовать параметры системного прокси-сервера.
  • Linux: Чтение конфигурации прокси-сервера из переменных среды, или, если переменные не определены, инициализация неконфигурированного экземпляра для обхода всех адресов.

Инициализация свойств DefaultProxy на платформах под управлением Windows и Unix использует следующие переменные среды:

  • HTTP_PROXY: прокси-сервер, используемый в HTTP-запросах.
  • HTTPS_PROXY: прокси-сервер, используемый в HTTPS-запросах.
  • ALL_PROXY: прокси-сервер, используемый в HTTP-запросах и/или HTTPS, когда переменные HTTP_PROXY и/или HTTPS_PROXY не определены.
  • NO_PROXY: список имен узлов, разделенных запятыми, которые следует исключить из прокси-сервера. Символы "звездочка" не поддерживаются в качестве подстановочных знаков. Используйте ведущий период (.), если вы хотите соответствовать поддомену. Примеры: NO_PROXY=.example.com (с ведущим периодом) соответствует www.example.com, но не соответствует example.com. NO_PROXY=example.com (без начального периода) не соответствует www.example.com. Это поведение может быть пересмотрено в будущем, чтобы соответствовать другим экосистемам лучше.

В системах, где переменные среды чувствительны к регистру, имена переменных могут быть только строчными или только прописными. Сначала проверяются имена в нижнем регистре.

Прокси-сервер может быть именем узла или IP-адресом, за которыми при необходимости следуют двоеточие и номер порта, или URL-адрес http, опционально включая имя пользователя и пароль для аутентификации на прокси-сервере. URL-адрес должен начинаться с http, а не https, и не может содержать текст после имени узла, IP-адреса или порта.

Настройте прокси-сервер для каждого клиента

Свойство HttpClientHandler.Proxy определяет объект WebProxy для обработки запросов к интернет-ресурсам. Чтобы указать, что прокси-сервер не должен использоваться, задайте свойству Proxy значение экземпляра прокси, возвращаемого методом GlobalProxySelection.GetEmptyWebProxy().

Локальный компьютер или файл конфигурации приложения может указать, что используется прокси-сервер по умолчанию. Если задано свойство Proxy, то параметры прокси-сервера из свойства Proxy переопределяют локальный компьютер или файл конфигурации приложения, а обработчик использует указанные параметры прокси-сервера. Если прокси-сервер не указан в файле конфигурации, а свойство Proxy не указано, обработчик использует параметры прокси-сервера, унаследованные от локального компьютера. Если параметры прокси-сервера отсутствуют, запрос отправляется непосредственно серверу.

Класс HttpClientHandler анализирует список обхода прокси-сервера с подстановочными знаками, унаследованными от параметров локального компьютера. Например, HttpClientHandler класс анализирует список обходов "nt*" из браузеров как регулярное выражение "nt.*". Поэтому URL-адрес http://nt.com обходит прокси-сервер, используя класс HttpClientHandler.

Класс HttpClientHandler поддерживает обход локального прокси-сервера. Класс считает назначение локальным, если выполняются какие-либо из следующих условий:

  • Назначение содержит плоское имя (без точек в URL-адресе).
  • Назначение содержит адрес обратного цикла (Loopback или IPv6Loopback) или назначение содержит свойство IPAddress, назначенное локальному компьютеру.
  • Суффикс домена назначения соответствует суффиксу домена локального компьютера, как определено в свойстве DomainName.

Дополнительные сведения о настройке прокси-сервера см. в следующих API:

Дальнейшие действия