Compartilhar via


Fazer solicitações HTTP com a classe HttpClient

Neste artigo, você aprenderá a fazer solicitações HTTP e lidar com respostas com a classe HttpClient.

Importante

Todas as solicitações HTTP de exemplo neste artigo têm como destino uma das seguintes URLs:

Os pontos de extremidade HTTP normalmente retornam dados JSON (JavaScript Object Notation), mas nem sempre. Por conveniência, o pacote NuGet System.Net.Http.Json opcional fornece vários métodos de extensão para objetos HttpClient e HttpContent que executam serialização e desserialização automáticas usando o pacote 📦 System.Text.Json NuGet. Os exemplos neste artigo chamam a atenção para locais em que essas extensões estão disponíveis.

Dica

Todo o código-fonte referenciado neste artigo está disponível no GitHub: .NET Docs repositório.

Criar um objeto HttpClient

A maioria dos exemplos neste artigo reutiliza a mesma instância de HttpClient, para que você possa configurar a instância uma vez e usá-la para os exemplos restantes. Para criar um objeto HttpClient, use o construtor de classe HttpClient. Para obter mais informações, confira Diretrizes para usar 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"),
};

O código conclui as seguintes tarefas:

  • Instancie uma nova instância de HttpClient como uma variável static. De acordo com as diretrizes de, a abordagem recomendada é reutilizar as instâncias HttpClient durante o ciclo de vida do aplicativo.
  • Defina a propriedade HttpClient.BaseAddress como "https://jsonplaceholder.typicode.com".

Essa instância HttpClient usa o endereço base para fazer solicitações subsequentes. Para aplicar outras configurações, considere as seguintes APIs:

Dica

Como alternativa, você pode criar instâncias HttpClient usando uma abordagem de padrão de fábrica que permite configurar qualquer número de clientes e consumi-las como serviços de injeção de dependência. Para obter mais informações, consulte fábrica de clientes HTTP com o .NET.

Fazer uma solicitação HTTP

Para fazer uma solicitação HTTP, você chama qualquer um dos seguintes métodos de API:

Método 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

uma solicitação de USER SPECIFIED indica que o método SendAsync aceita qualquer objeto de HttpMethod válido.

Aviso

Fazer solicitações HTTP é considerado um trabalho associado a E/S de rede. Existe um método de HttpClient.Send síncrono, mas a recomendação é usar as APIs assíncronas, a menos que você tenha um bom motivo para não fazer isso.

Observação

Ao direcionar o desenvolvimento para dispositivos Android (como no .NET MAUI), você deve adicionar a definição android:usesCleartextTraffic="true" à seção <application></application> no arquivo AndroidManifest.xml. Essa configuração habilita o tráfego de texto limpo, como solicitações HTTP, que de outra forma está desabilitada por padrão devido às políticas de segurança do Android. Considere o seguinte exemplo de configurações 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>

Para obter mais informações, consulte Habilitar o tráfego de rede de texto claro para o domínio localhost.

Entender o conteúdo HTTP

O tipo HttpContent é usado para representar um corpo de entidade HTTP e cabeçalhos de conteúdo correspondentes. Para métodos HTTP (ou métodos de solicitação) que exigem um corpo (POST, PUT, PATCH), você usa a classe HttpContent para especificar o corpo da solicitação. A maioria dos exemplos mostra como preparar a subclasse StringContent com um conteúdo JSON, mas existem outras subclasses para diferentes tipos de conteúdo (MIME).

A classe HttpContent também é usada para representar o corpo da resposta da classe HttpResponseMessage, que é acessível na propriedade HttpResponseMessage.Content.

Usar uma solicitação HTTP GET

Uma solicitação GET não deve enviar um corpo. Essa solicitação é usada (como o nome do método indica) para recuperar (ou obter) dados de um recurso. Para fazer uma solicitação http GET dada uma instância de HttpClient e um objeto Uri, use o método 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
    //   }
}

O código conclui as seguintes tarefas:

  • Faça uma solicitação GET ao endpoint "https://jsonplaceholder.typicode.com/todos/3".
  • Verifique se a resposta foi bem-sucedida.
  • Escreva os detalhes da solicitação no console.
  • Leia o corpo da resposta como uma cadeia de caracteres.
  • Grave o corpo da resposta JSON no console.

O método WriteRequestToConsole é uma extensão personalizada que não faz parte da estrutura. Se você estiver curioso sobre a implementação, considere o seguinte código 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}");        
    }
}

Essa funcionalidade é usada para gravar os detalhes da solicitação no console no seguinte formulário:

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

Por exemplo, a solicitação GET para o endpoint "https://jsonplaceholder.typicode.com/todos/3" gera a seguinte mensagem:

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

Criar a solicitação HTTP GET do JSON

O ponto de extremidade https://jsonplaceholder.typicode.com/todos retorna uma matriz JSON de objetos Todo. Sua estrutura JSON é semelhante à seguinte forma:

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

O objeto Todo do C# é definido da seguinte maneira:

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

É um tipo record class, com propriedades opcionais Id, Title, Completed e UserId. Para obter mais informações sobre o tipo record, confira Introdução aos tipos de registro em C#. Para desserializar automaticamente solicitações GET em um objeto C# fortemente tipado, use o método de extensão GetFromJsonAsync que faz parte do pacote 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 }
}

O código conclui as seguintes tarefas:

  • Faça uma solicitação GET para "https://jsonplaceholder.typicode.com/todos?userId=1&completed=false".

    A cadeia de caracteres de consulta representa os critérios de filtragem da solicitação. Quando o comando é bem-sucedido, a resposta é desserializada automaticamente em um objeto List<Todo>.

  • Escreva os detalhes da solicitação no console, juntamente com cada objeto Todo.

Usar uma solicitação HTTP POST

Uma solicitação POST envia dados ao servidor para processamento. O cabeçalho da solicitação Content-Type significa qual tipo MIME o corpo está enviando. Para fazer uma solicitação http POST dada uma instância de HttpClient e um objeto Uri, use o método 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
    //   }
}

O código conclui as seguintes tarefas:

  • ** Prepare a instância StringContent com o corpo JSON da solicitação (tipo MIME de "application/json").
  • Faça uma solicitação POST para o endpoint "https://jsonplaceholder.typicode.com/todos".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação no console.
  • Grave o corpo da resposta como uma cadeia de caracteres no console.

Criar a solicitação HTTP POST como JSON

Para serializar automaticamente argumentos de solicitação POST e desserializar respostas em objetos C# fortemente tipados, use o método de extensão PostAsJsonAsync que faz parte do pacote 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 }
}

O código conclui as seguintes tarefas:

  • Serialize a instância de Todo como JSON e faça uma solicitação POST para o endpoint "https://jsonplaceholder.typicode.com/todos".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação no console.
  • Desserialize o corpo da resposta em uma instância Todo e grave o objeto Todo no console.

Usar uma solicitação HTTP PUT

O método de solicitação PUT substitui um recurso existente ou cria um novo usando o conteúdo do corpo da solicitação. Para fazer uma solicitação http PUT dada uma instância de HttpClient e um objeto Uri, use o método 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
    //   }
}

O código conclui as seguintes tarefas:

  • Prepare uma instância StringContent com o corpo JSON da solicitação (tipo MIME de "application/json").
  • Faça uma solicitação PUT para o endpoint "https://jsonplaceholder.typicode.com/todos/1".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação com o corpo da resposta JSON no console.

Criar a solicitação HTTP PUT como JSON

Para serializar automaticamente argumentos de solicitação PUT e desserializar respostas em objetos C# fortemente tipados, use o método de extensão PutAsJsonAsync que faz parte do pacote 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 }
}

O código conclui as seguintes tarefas:

  • Serialize a instância de Todo como JSON e faça uma solicitação PUT para o endpoint "https://jsonplaceholder.typicode.com/todos/5".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação no console.
  • Desserialize o corpo da resposta em uma instância Todo e grave os objetos Todo no console.

Usar uma solicitação HTTP PATCH

A solicitação PATCH é uma atualização parcial de um recurso existente. Essa solicitação não cria um novo recurso e não se destina a substituir um recurso existente. Em vez disso, esse método atualiza apenas parcialmente um recurso. Para fazer uma solicitação http PATCH dada uma instância de HttpClient e um objeto Uri, use o método 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
    //   }
}

O código conclui as seguintes tarefas:

  • Prepare uma instância StringContent com o corpo JSON da solicitação (tipo MIME de "application/json").
  • Faça uma solicitação PATCH para o endpoint "https://jsonplaceholder.typicode.com/todos/1".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação com o corpo da resposta JSON no console.

Não existem métodos de extensão para solicitações PATCH no pacote NuGet System.Net.Http.Json.

Usar uma solicitação HTTP DELETE

Uma solicitação DELETE remove um recurso existente e a solicitação é idempotente, mas não é segura. Várias solicitações DELETE para os mesmos recursos produzem o mesmo resultado, mas a solicitação afeta o estado do recurso. Para fazer uma solicitação http DELETE dada uma instância de HttpClient e um objeto Uri, use o método 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
    //   {}
}

O código conclui as seguintes tarefas:

  • Realize uma solicitação DELETE para o endpoint "https://jsonplaceholder.typicode.com/todos/1".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação no console.

Dica

A resposta a uma solicitação DELETE (assim como uma solicitação PUT) pode ou não incluir um corpo.

Explorar a solicitação HTTP HEAD

A solicitação HEAD é semelhante a uma solicitação GET. Em vez de retornar o recurso, essa solicitação retorna apenas os cabeçalhos associados ao recurso. Uma resposta à solicitação HEAD não retorna um corpo. Para fazer uma solicitação http HEAD dada uma instância de HttpClient e um objeto Uri, use o método HttpClient.SendAsync com o tipo HttpMethod definido como 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
}

O código conclui as seguintes tarefas:

  • Faça uma solicitação HEAD ao endpoint "https://www.example.com/".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação no console.
  • Itere em todos os cabeçalhos de resposta e grave cada cabeçalho no console.

Explorar a solicitação HTTP OPTIONS

A solicitação OPTIONS é usada para identificar a quais métodos HTTP um servidor ou ponto de extremidade dá suporte. Para fazer uma solicitação http OPTIONS dada uma instância de HttpClient e um objeto Uri, use o método HttpClient.SendAsync com o tipo HttpMethod definido como 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
}

O código conclui as seguintes tarefas:

  • Envie uma solicitação HTTP OPTIONS para o ponto de extremidade "https://www.example.com/".
  • Verifique se a resposta foi bem-sucedida e escreva os detalhes da solicitação no console.
  • Itere em todos os cabeçalhos de conteúdo de resposta e grave cada cabeçalho no console.

Explorar a solicitação HTTP TRACE

A solicitação TRACE pode ser útil para a depuração, pois fornece retorno da mensagem de solicitação ao nível da aplicação. Para fazer uma solicitação http TRACE, crie um HttpRequestMessage usando o tipo HttpMethod.Trace:

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

Cuidado

Nem todos os servidores HTTP dão suporte ao método HTTP TRACE. Esse método pode expor uma vulnerabilidade de segurança se usado de forma imprudente. Para obter mais informações, confira o texto sobre Rastreamento entre sites da OWASP (Open Web Application Security Project).

Lidar com uma resposta HTTP

Ao lidar com uma resposta HTTP, você interage com o tipo HttpResponseMessage. Vários membros são usados para avaliar a validade de uma resposta. O código de status HTTP está disponível na propriedade HttpResponseMessage.StatusCode.

Suponha que você envie uma solicitação determinada a uma instância do cliente:

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

Para garantir que o response seja OK (código de status HTTP 200), você pode avaliar o valor, conforme mostrado no exemplo a seguir:

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

Há outros códigos de status HTTP que representam uma resposta bem-sucedida, como CREATED (código de status HTTP 201), ACCEPTED (código de status HTTP 202), NO CONTENT (código de status HTTP 204) e RESET CONTENT (código de status HTTP 205). Você também pode usar a propriedade HttpResponseMessage.IsSuccessStatusCode para avaliar esses códigos, o que garante que o código de status de resposta esteja dentro do intervalo de 200 a 299:

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

Se você precisar que a estrutura gere o erro HttpRequestException, você pode chamar o método HttpResponseMessage.EnsureSuccessStatusCode():

response.EnsureSuccessStatusCode();

Esse código gera um erro HttpRequestException se o código de status de resposta não estiver dentro do intervalo 200-299.

Explorar respostas de conteúdo válidas para HTTP

Com uma resposta válida, você pode acessar o corpo da resposta usando a propriedade Content. O corpo está disponível como uma instância HttpContent, que você pode usar para acessar o corpo como um fluxo, matriz de bytes ou cadeia de caracteres.

O código a seguir usa o objeto responseStream para ler o corpo da resposta:

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

Você pode usar diferentes objetos para ler o conteúdo da resposta. Use o objeto responseByteArray para ler o corpo da resposta:

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

Use o objeto responseString para ler o corpo da resposta:

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

Quando você sabe que um ponto de extremidade HTTP retorna JSON, você pode desserializar o corpo da resposta em qualquer objeto C# válido usando o pacote NuGet System.Net.Http.Json:

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

Neste código, o valor result é o corpo da resposta desserializado como o tipo T.

Usar o tratamento de erros HTTP

Quando uma solicitação HTTP falha, o sistema lança o objeto HttpRequestException. Capturar apenas a exceção pode não ser suficiente. Há outras possíveis exceções geradas que você talvez considere tratar. Por exemplo, o código de chamada pode usar um token de cancelamento que foi cancelado antes da conclusão da solicitação. Neste cenário, você pode capturar o erro 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}");
}

Da mesma forma, quando você faz uma solicitação HTTP, se o servidor não responder antes que o valor do HttpClient.Timeout seja excedido, a mesma exceção será gerada. Nesse cenário, você pode identificar que ocorreu um timeout avaliando a propriedade Exception.InnerException ao capturar o erro 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}");
}

No código, quando a exceção interna é um tipo de TimeoutException, o tempo limite ocorreu e o token de cancelamento não cancela a solicitação.

Para avaliar o código de status HTTP ao capturar o objeto HttpRequestException, você pode avaliar a propriedade 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}");
}

No código, o método EnsureSuccessStatusCode() é chamado para gerar uma exceção se a resposta não for bem-sucedida. A propriedade HttpRequestException.StatusCode é avaliada em seguida para determinar se a resposta foi um 404 (código de status HTTP 404). Há vários métodos auxiliares no objeto HttpClient que chamam implicitamente o método EnsureSuccessStatusCode em seu nome.

Para o tratamento de erros HTTP, considere as seguintes APIs:

Dica

Todos os métodos HttpClient usados para fazer solicitações HTTP que não retornam um tipo de HttpResponseMessage chamam implicitamente o método EnsureSuccessStatusCode em seu nome.

Ao chamar esses métodos, você pode manipular o objeto HttpRequestException e avaliar a propriedade HttpRequestException.StatusCode para determinar o código de status HTTP da resposta:

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}");
}

Pode haver cenários em que você precisa lançar o objeto HttpRequestException em seu código. O construtor HttpRequestException() é público e você pode usá-lo para gerar uma exceção com uma mensagem personalizada:

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}");
}

Configurar um proxy HTTP

Um proxy HTTP pode ser configurado de duas maneiras. Um padrão é especificado na propriedade HttpClient.DefaultProxy. Como alternativa, você pode especificar um proxy na propriedade HttpClientHandler.Proxy.

Usar um proxy padrão global

A propriedade HttpClient.DefaultProxy é uma propriedade estática que determina o proxy padrão que todas as instâncias HttpClient usam, se nenhum proxy for definido explicitamente no objeto HttpClientHandler passado por seu construtor.

A instância padrão retornada por essa propriedade inicializa de acordo com um conjunto diferente de regras, dependendo da plataforma:

  • Windows: Leia a configuração de proxy das variáveis de ambiente ou, se as variáveis não estiverem definidas, leia as configurações de proxy do usuário.
  • macOS: leia a configuração de proxy com base em variáveis de ambiente ou, se as variáveis não estiverem definidas, leia com base nas configurações de proxy do sistema.
  • Linux: ler a configuração de proxy das variáveis de ambiente ou, se as variáveis não estiverem definidas, inicializar uma instância não configurada para ignorar todos os endereços.

A inicialização da propriedade DefaultProxy em plataformas baseadas em Windows e Unix usa as seguintes variáveis de ambiente:

  • HTTP_PROXY: o servidor proxy usado em solicitações HTTP.
  • HTTPS_PROXY: o servidor proxy usado em solicitações HTTPS.
  • ALL_PROXY: o servidor proxy usado em solicitações HTTP e/ou HTTPS quando as variáveis HTTP_PROXY e/ou HTTPS_PROXY não são definidas.
  • NO_PROXY: uma lista separada por vírgulas de nomes de host a serem excluídos do proxy. Não há suporte para asteriscos para curingas. Use um ponto (.) quando quiser corresponder a um subdomínio. Exemplos: NO_PROXY=.example.com (com ponto à esquerda) corresponde a www.example.com, mas não corresponde a example.com. NO_PROXY=example.com (sem o ponto inicial) não corresponde a www.example.com. Esse comportamento pode ser revisitado no futuro para corresponder melhor a outros ecossistemas.

Em sistemas nos quais as variáveis de ambiente diferenciam maiúsculas de minúsculas, os nomes de variáveis podem ter apenas minúsculas ou apenas maiúsculas. Os nomes em minúsculas são verificados primeiro.

O servidor proxy pode ser um nome de host ou endereço IP, seguido opcionalmente por dois pontos e um número de porta, ou pode ser uma URL http, incluindo, se necessário, um nome de usuário e uma senha para autenticação do proxy. A URL deve começar com http, não httpse não pode incluir nenhum texto após o nome do host, IP ou porta.

Configurar o proxy por cliente

A propriedade HttpClientHandler.Proxy identifica o objeto WebProxy a ser usado para processar solicitações para recursos da Internet. Para especificar que nenhum proxy deve ser usado, defina a propriedade Proxy como a instância de proxy retornada pelo método GlobalProxySelection.GetEmptyWebProxy().

O computador local ou o arquivo de configuração do aplicativo pode especificar que um proxy padrão é usado. Se a propriedade Proxy for especificada, as configurações de proxy da propriedade Proxy substituirão o arquivo de configuração de aplicativo ou computador local e o manipulador usará as configurações de proxy especificadas. Se nenhum proxy for especificado em um arquivo de configuração e a propriedade Proxy não for especificada, o manipulador usará as configurações de proxy herdadas do computador local. Se não houver configurações de proxy, a solicitação será enviada diretamente ao servidor.

A classe HttpClientHandler analisa uma lista de bypass de proxy com caracteres curinga herdados das configurações do computador local. Por exemplo, a classe HttpClientHandler interpreta uma lista de bypass de "nt*" dos navegadores como uma expressão regular de "nt.*". Portanto, uma URL de http://nt.com ignora o proxy usando a classe HttpClientHandler.

A classe HttpClientHandler dá suporte ao bypass de proxy local. A classe considera um destino como local se qualquer uma das seguintes condições for atendida:

  • O destino contém um nome simples (sem períodos (.) na URL).
  • O destino contém um endereço de loopback (Loopback ou IPv6Loopback) ou o destino contém uma propriedade IPAddress atribuída ao computador local.
  • O sufixo de domínio do destino corresponde ao sufixo de domínio do computador local, conforme definido na propriedade DomainName.

Para obter mais informações sobre como configurar um proxy, consulte as seguintes APIs:

Próximas etapas