Guia de migração do HttpWebRequest para o HttpClient

Este artigo tem como objetivo orientar os desenvolvedores no processo de migração do HttpWebRequest, ServicePoint e ServicePointManager para HttpClient. A migração é necessária devido à obsolescência das APIs mais antigas e aos inúmeros benefícios oferecidos pelo HttpClient, incluindo melhor desempenho, melhor gerenciamento de recursos e um design de API mais moderno e flexível. Seguindo as etapas descritas neste documento, os desenvolvedores poderão fazer a transição de suas bases de código sem problemas e aproveitar ao máximo os recursos fornecidos pelo HttpClient.

Aviso

Migrar do HttpWebRequest, ServicePoint e ServicePointManager para HttpClient não é apenas uma melhoria de desempenho "boa de se ter". É crucial entender que o desempenho da lógica WebRequest existente provavelmente será degradado significativamente quando você mudar para o .NET (Core). Isso acontece porque WebRequest é mantido como uma camada de compatibilidade mínima, o que significa que carece de muitas otimizações, como reutilização de conexão em vários casos. Portanto, a transição para HttpClient é essencial para garantir que o desempenho e o gerenciamento de recursos do seu aplicativo estejam de acordo com os padrões modernos.

Migrar de HttpWebRequest para HttpClient

Vamos começar com alguns exemplos:

Solicitação GET simples usando HttpWebRequest

Aqui está um exemplo de como o código pode ser:

HttpWebRequest request = WebRequest.CreateHttp(uri);
using WebResponse response = await request.GetResponseAsync();

Solicitação GET simples usando HttpClient

Aqui está um exemplo de como o código pode ser:

HttpClient client = new();
using HttpResponseMessage message = await client.GetAsync(uri);

Solicitação POST simples usando HttpWebRequest

Aqui está um exemplo de como o código pode ser:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.ContentType = "text/plain";
await using Stream stream = await request.GetRequestStreamAsync();
await stream.WriteAsync("Hello World!"u8.ToArray());
using WebResponse response = await request.GetResponseAsync();

Solicitação POST simples usando HttpClient

Aqui está um exemplo de como o código pode ser:

HttpClient client = new();
using HttpResponseMessage responseMessage = await client.PostAsync(uri, new StringContent("Hello World!"));

Guia de migração do HttpWebRequest para HttpClient, SocketsHttpHandler

HttpWebRequestAPI antigo Nova API Observações
Accept Accept Exemplo: Definir cabeçalhos de solicitação.
Address RequestUri Exemplo: Buscar URI redirecionado.
AllowAutoRedirect AllowAutoRedirect Exemplo: Definindo propriedades SocketsHttpHandler.
AllowReadStreamBuffering Nenhuma API equivalente direta. Uso de propriedades de buffer.
AllowWriteStreamBuffering Nenhuma API equivalente direta. Uso de propriedades de buffer.
AuthenticationLevel Nenhuma API equivalente direta. Exemplo: Habilitando a autenticação mútua.
AutomaticDecompression AutomaticDecompression Exemplo: Definindo propriedades SocketsHttpHandler.
CachePolicy Nenhuma API equivalente direta. Exemplo: Aplicar cabeçalhos de CachePolicy.
ClientCertificates SslOptions.ClientCertificates Uso de propriedades relacionadas ao certificado em HttpClient.
Connection Connection Exemplo: Definir cabeçalhos de solicitação.
ConnectionGroupName Não há nenhuma API equivalente. Sem solução alternativa
ContentLength ContentLength Exemplo: Definir cabeçalhos de conteúdo.
ContentType ContentType Exemplo: Definir cabeçalhos de conteúdo.
ContinueDelegate Não há nenhuma API equivalente. Sem solução alternativa.
ContinueTimeout Expect100ContinueTimeout Exemplo: Definir propriedades SocketsHttpHandler.
CookieContainer CookieContainer Exemplo: Definir propriedades SocketsHttpHandler.
Credentials Credentials Exemplo: Definir propriedades SocketsHttpHandler.
Date Date Exemplo: Definir cabeçalhos de solicitação.
DefaultCachePolicy Nenhuma API equivalente direta. Exemplo: Aplicar cabeçalhos de CachePolicy.
DefaultMaximumErrorResponseLength Nenhuma API equivalente direta. Exemplo: Definir MaximumErrorResponseLength em HttpClient.
DefaultMaximumResponseHeadersLength Não há nenhuma API equivalente. MaxResponseHeadersLength pode ser usado em vez disso.
DefaultWebProxy Não há nenhuma API equivalente. Proxy pode ser usado em vez disso.
Expect Expect Exemplo: Definir cabeçalhos de solicitação.
HaveResponse Não há nenhuma API equivalente. Implícito por ter uma instância HttpResponseMessage.
Headers Headers Exemplo: Definir cabeçalhos de solicitação.
Host Host Exemplo: Definir cabeçalhos de solicitação.
IfModifiedSince IfModifiedSince Exemplo: Definir cabeçalhos de solicitação.
ImpersonationLevel Nenhuma API equivalente direta. Exemplo: Alterar ImpersonationLevel.
KeepAlive Nenhuma API equivalente direta. Exemplo: Definir cabeçalhos de solicitação.
MaximumAutomaticRedirections MaxAutomaticRedirections Exemplo: Definindo propriedades SocketsHttpHandler.
MaximumResponseHeadersLength MaxResponseHeadersLength Exemplo: Definindo propriedades SocketsHttpHandler.
MediaType Nenhuma API equivalente direta. Exemplo: Definir cabeçalhos de conteúdo.
Method Method Exemplo: Uso de propriedades HttpRequestMessage.
Pipelined Não há nenhuma API equivalente. HttpClient não suporta pipelining.
PreAuthenticate PreAuthenticate
ProtocolVersion HttpRequestMessage.Version Exemplo: Uso de propriedades HttpRequestMessage.
Proxy Proxy Exemplo: Definindo propriedades SocketsHttpHandler.
ReadWriteTimeout Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.
Referer Referrer Exemplo: Definir cabeçalhos de solicitação.
RequestUri RequestUri Exemplo: Uso de propriedades HttpRequestMessage.
SendChunked TransferEncodingChunked Exemplo: Definir cabeçalhos de solicitação.
ServerCertificateValidationCallback SslOptions.RemoteCertificateValidationCallback Exemplo: Definindo propriedades SocketsHttpHandler.
ServicePoint Não há nenhuma API equivalente. ServicePoint não faz parte do HttpClient.
SupportsCookieContainer Não há nenhuma API equivalente. Isso é sempre true para HttpClient.
Timeout Timeout
TransferEncoding TransferEncoding Exemplo: Definir cabeçalhos de solicitação.
UnsafeAuthenticatedConnectionSharing Não há nenhuma API equivalente. Sem solução alternativa
UseDefaultCredentials Nenhuma API equivalente direta. Exemplo: Definindo propriedades SocketsHttpHandler.
UserAgent UserAgent Exemplo: Definir cabeçalhos de solicitação.

Migrar o uso do ServicePoint(Manager)

Você deve estar ciente de que ServicePointManager é uma classe estática, o que significa que quaisquer feitas em suas propriedades terão um efeito global em todos os objetos ServicePoint recém-criados dentro do aplicativo. Por exemplo, quando você modifica uma propriedade como ConnectionLimit ou Expect100Continue, isso afeta cada nova instância do ServicePoint.

Aviso

No .NET moderno, HttpClient não leva em conta nenhuma configuração definida no ServicePointManager.

Mapeamento das propriedades ServicePointManager

ServicePointManagerAPI antigo Nova API Observações
CheckCertificateRevocationList SslOptions.CertificateRevocationCheckMode Exemplo: Habilitando a verificação de CRL com SocketsHttpHandler.
DefaultConnectionLimit MaxConnectionsPerServer Exemplo: Definindo propriedades SocketsHttpHandler.
DnsRefreshTimeout Não há nenhuma API equivalente. Exemplo: Habilitando o Dns Round Robin.
EnableDnsRoundRobin Não há nenhuma API equivalente. Exemplo: Habilitando o Dns Round Robin.
EncryptionPolicy SslOptions.EncryptionPolicy Exemplo: Definindo propriedades SocketsHttpHandler.
Expect100Continue ExpectContinue Exemplo: Definir cabeçalhos de solicitação.
MaxServicePointIdleTime PooledConnectionIdleTimeout Exemplo: Definindo propriedades SocketsHttpHandler.
MaxServicePoints Não há nenhuma API equivalente. ServicePoint não faz parte do HttpClient.
ReusePort Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.
SecurityProtocol SslOptions.EnabledSslProtocols Exemplo: Definindo propriedades SocketsHttpHandler.
ServerCertificateValidationCallback SslOptions.RemoteCertificateValidationCallback Ambos são RemoteCertificateValidationCallback
UseNagleAlgorithm Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.

Aviso

No .NET moderno, os valores padrão para as propriedades UseNagleAlgorithm e Expect100Continue são definidos como false. Esses valores eram true por padrão no .NET Framework.

Mapeamento do método ServicePointManager

ServicePointManagerAPI antigo Nova API Observações
FindServicePoint Não há nenhuma API equivalente. Sem solução alternativa
SetTcpKeepAlive Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.

Mapeamento das propriedades ServicePoint

ServicePointAPI antigo Nova API Observações
Address HttpRequestMessage.RequestUri Este é o uri de solicitação, essas informações podem ser encontradas em HttpRequestMessage.
BindIPEndPointDelegate Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.
Certificate Nenhuma API equivalente direta. Essas informações podem ser obtidas em RemoteCertificateValidationCallback. Exemplo: Buscar certificado.
ClientCertificate Não há nenhuma API equivalente. Exemplo: Habilitando a autenticação mútua.
ConnectionLeaseTimeout SocketsHttpHandler.PooledConnectionLifetime Configuração equivalente em HttpClient
ConnectionLimit MaxConnectionsPerServer Exemplo: Definindo propriedades SocketsHttpHandler.
ConnectionName Não há nenhuma API equivalente. Sem solução alternativa
CurrentConnections Não há nenhuma API equivalente. Consulte Telemetria de rede no .NET.
Expect100Continue ExpectContinue Exemplo: Definir cabeçalhos de solicitação.
IdleSince Não há nenhuma API equivalente. Sem solução alternativa
MaxIdleTime PooledConnectionIdleTimeout Exemplo: Definindo propriedades SocketsHttpHandler.
ProtocolVersion HttpRequestMessage.Version Exemplo: Uso de propriedades HttpRequestMessage.
ReceiveBufferSize Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.
SupportsPipelining Não há nenhuma API equivalente. HttpClient não suporta pipelining.
UseNagleAlgorithm Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.

Mapeamento do método ServicePoint

ServicePointAPI antigo Nova API Observações
CloseConnectionGroup Sem equivalente Sem solução alternativa
SetTcpKeepAlive Nenhuma API equivalente direta. Uso de SocketsHttpHandler e ConnectCallback.

Uso das propriedades HttpClient e HttpRequestMessage

Ao trabalhar com HttpClient no .NET, você tem acesso a uma variedade de propriedades que permitem configurar e personalizar solicitações e respostas HTTP. Entender essas propriedades pode ajudá-lo a aproveitar ao máximo o HttpClient e garantir que seu aplicativo se comunique de forma eficiente e segura com os serviços Web.

Exemplo: Uso de propriedades HttpRequestMessage

Aqui está um exemplo de como usar HttpClient e HttpRequestMessage juntos:

var client = new HttpClient();

var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com"); // Method and RequestUri usage
var request = new HttpRequestMessage() // Alternative way to set RequestUri and Method
{
    RequestUri = new Uri("https://example.com"),
    Method = HttpMethod.Post
};
request.Headers.Add("Custom-Header", "value");
request.Content = new StringContent("somestring");

using var response = await client.SendAsync(request);
var protocolVersion = response.RequestMessage.Version; // Fetch `ProtocolVersion`.

Exemplo: Buscar URI redirecionado

Aqui está um exemplo de como buscar o URI redirecionado (o mesmo que HttpWebRequest.Address):

var client = new HttpClient();
using var response = await client.GetAsync(uri);
var redirectedUri = response.RequestMessage.RequestUri;

Uso de SocketsHttpHandler e ConnectCallback

A propriedade ConnectCallback em SocketsHttpHandler permite que os desenvolvedores personalizem o processo de estabelecimento de uma conexão TCP. Isso pode ser útil para cenários em que você precisa controlar a resolução DNS ou aplicar opções de soquete específicas na conexão. Usando ConnectCallback, você pode interceptar e modificar o processo de conexão antes que ele seja usado pelo HttpClient.

Exemplo: vincular o endereço IP ao soquete

Na abordagem antiga, usando HttpWebRequestpode ter usado lógica personalizada para vincular um endereço IP específico a um soquete. Veja como você pode obter uma funcionalidade semelhante usando HttpClient e ConnectCallback:

Código antigo usando HttpWebRequest:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
{
    // Bind to a specific IP address
    IPAddress localAddress = IPAddress.Parse("192.168.1.100");
    return new IPEndPoint(localAddress, 0);
};
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

Novo código usando HttpClient e ConnectCallback:

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        // Bind to a specific IP address
        IPAddress localAddress = IPAddress.Parse("192.168.1.100");
        var socket = new Socket(localAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            socket.Bind(new IPEndPoint(localAddress, 0));
            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

Exemplo: Aplicar opções de soquete específicas

Se você precisar aplicar opções de soquete específicas, como habilitar o TCP keep-alive, poderá usar ConnectCallback para configurar o soquete antes que ele seja usado pelo HttpClient. Na verdade, ConnectCallback é mais flexível configurar opções de soquete.

Código antigo usando HttpWebRequest:

ServicePointManager.ReusePort = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.SetTcpKeepAlive(true, 60000, 1000);
request.ServicePoint.ReceiveBufferSize = 8192;
request.ServicePoint.UseNagleAlgorithm = false;
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

Novo código usando HttpClient e ConnectCallback:

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        try
        {
            // Setting TCP Keep Alive
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 60);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 1);

            // Setting ReceiveBufferSize
            socket.ReceiveBufferSize = 8192;

            // Enabling ReusePort
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, true);

            // Disabling Nagle Algorithm
            socket.NoDelay = true;

            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

Exemplo: Habilitar round robin do DNS

DNS Round Robin é uma técnica usada para distribuir o tráfego de rede em vários servidores, alternando entre uma lista de endereços IP associados a um único nome de domínio. Isso ajuda no balanceamento de carga e na melhoria da disponibilidade dos serviços. Ao usar HttpClient, você pode implementar o DNS Round Robin manipulando manualmente a resolução DNS e girando pelos endereços IP usando a propriedade ConnectCallback de SocketsHttpHandler.

Para habilitar o DNS Round Robin com HttpClient, você pode usar a propriedade ConnectCallback para resolver manualmente as entradas DNS e alternar entre os endereços IP. Este é um exemplo para HttpWebRequest e HttpClient:

Código antigo usando HttpWebRequest:

ServicePointManager.DnsRefreshTimeout = 60000;
ServicePointManager.EnableDnsRoundRobin = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

Na API HttpWebRequest mais antiga, habilitar o DNS Round Robin era simples devido ao suporte integrado a esse recurso. No entanto, a API HttpClient mais recente não fornece a mesma funcionalidade interna. Apesar disso, você pode obter um comportamento semelhante implementando um DnsRoundRobinConnector que rotaciona manualmente os endereços IP retornados pela resolução DNS.

Novo código usando HttpClient:

// This is available as NuGet Package: https://www.nuget.org/packages/DnsRoundRobin/
// The original source code can be found also here: https://github.com/MihaZupan/DnsRoundRobin
public sealed class DnsRoundRobinConnector : IDisposable

Você pode encontrar a implementação de DnsRoundRobinConnector aqui.

DnsRoundRobinConnector Uso:

private static readonly DnsRoundRobinConnector s_roundRobinConnector = new(
        dnsRefreshInterval: TimeSpan.FromSeconds(10),
        endpointConnectTimeout: TimeSpan.FromSeconds(5));
static async Task DnsRoundRobinConnectAsync()
{
    var handler = new SocketsHttpHandler
    {
        ConnectCallback = async (context, cancellation) =>
        {
            Socket socket = await DnsRoundRobinConnector.Shared.ConnectAsync(context.DnsEndPoint, cancellation);
            // Or you can create and use your custom DnsRoundRobinConnector instance
            // Socket socket = await s_roundRobinConnector.ConnectAsync(context.DnsEndPoint, cancellation);
            return new NetworkStream(socket, ownsSocket: true);
        }
    };
    var client = new HttpClient(handler);
    HttpResponseMessage response = await client.GetAsync(Uri);
}

Exemplo: Definir propriedades SocketsHttpHandler

SocketsHttpHandler é um manipulador poderoso e flexível no .NET que fornece opções de configuração avançadas para gerenciar conexões HTTP. Ao definir várias propriedades de SocketsHttpHandler, você pode ajustar o comportamento do cliente HTTP para atender a requisitos específicos, como otimização de desempenho, aprimoramentos de segurança e manipulação de conexão personalizada.

Aqui está um exemplo de como configurar SocketsHttpHandler com várias propriedades e usá-lo com HttpClient:

var cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie("cookieName", "cookieValue"));

var handler = new SocketsHttpHandler
{
    AllowAutoRedirect = true,
    AutomaticDecompression = DecompressionMethods.All,
    Expect100ContinueTimeout = TimeSpan.FromSeconds(1),
    CookieContainer = cookieContainer,
    Credentials = new NetworkCredential("user", "pass"),
    MaxAutomaticRedirections = 10,
    MaxResponseHeadersLength = 1,
    Proxy = new WebProxy("http://proxyserver:8080"), // Don't forget to set UseProxy
    UseProxy = true,
};

var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

Exemplo: Alterar ImpersonationLevel

Essa funcionalidade é específica para determinadas plataformas e está um pouco desatualizada. Se você precisar de uma solução alternativa, consulte esta seção do código.

Ao trabalhar com HttpClient, pode ser necessário manipular certificados de cliente para várias finalidades, como validação personalizada de certificados de servidor ou busca do certificado de servidor. HttpClient fornece várias propriedades e opções para gerenciar certificados de forma eficaz.

Exemplo: verificar a lista de revogação de certificados com SocketsHttpHandler

A propriedade CheckCertificateRevocationList em SocketsHttpHandler.SslOptions permite que os desenvolvedores habilitem ou desabilitem a verificação de listas de revogação de certificados (CRL) durante o handshake SSL/TLS. Habilitar essa propriedade garante que o cliente verifique se o certificado do servidor foi revogado, aumentando a segurança da conexão.

Código antigo usando HttpWebRequest:

ServicePointManager.CheckCertificateRevocationList = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

Novo código usando HttpClient:

bool checkCertificateRevocationList = true;
var handler = new SocketsHttpHandler
{
    SslOptions =
    {
        CertificateRevocationCheckMode = checkCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

Exemplo: Buscar certificado

Para buscar o certificado a partir do RemoteCertificateValidationCallback em HttpClient, você pode usar a propriedade ServerCertificateCustomValidationCallback de HttpClientHandler ou SocketsHttpHandler.SslOptions. Esse retorno de chamada permite que você inspecione o certificado do servidor durante o handshake SSL/TLS.

Código antigo usando HttpWebRequest:

HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
X509Certificate? serverCertificate = request.ServicePoint.Certificate;

Novo código usando HttpClient:

X509Certificate? serverCertificate = null;
var handler = new SocketsHttpHandler
{
    SslOptions = new SslClientAuthenticationOptions
    {
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            serverCertificate = certificate;

            // Leave the validation as-is.
            return sslPolicyErrors == SslPolicyErrors.None;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync("https://example.com");

Exemplo: Habilitar autenticação mútua

A autenticação mútua, também conhecida como SSL bidirecional ou autenticação de certificado do cliente, é um processo de segurança no qual o cliente e o servidor se autenticam. Isso garante que ambas as partes sejam quem afirmam ser, fornecendo uma camada adicional de segurança para comunicações confidenciais. No HttpClient, você pode ativar a autenticação mútua configurando o HttpClientHandler ou SocketsHttpHandler para incluir o certificado do cliente e validar o certificado do servidor.

Para habilitar a autenticação mútua, siga estas etapas:

  • Carregue o certificado do cliente.
  • Configure o HttpClientHandler ou SocketsHttpHandler para incluir o certificado do cliente.
  • Configure o retorno de chamada de validação do certificado do servidor se a validação personalizada for necessária.

Veja um exemplo usando SocketsHttpHandler:

var handler = new SocketsHttpHandler
{
    SslOptions = new SslClientAuthenticationOptions
    {
        ClientCertificates = new X509CertificateCollection
        {
            // Load the client certificate from a file
            new X509Certificate2("path_to_certificate.pfx", "certificate_password")
        },
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            // Custom validation logic for the server certificate
            return sslPolicyErrors == SslPolicyErrors.None;
        }
    }
};

var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

Uso de propriedades de cabeçalho

Os cabeçalhos desempenham um papel crucial na comunicação HTTP, fornecendo metadados essenciais sobre a solicitação e a resposta. Ao trabalhar com HttpClient no .NET, você pode definir e gerenciar várias propriedades de cabeçalho para controlar o comportamento de suas solicitações e respostas HTTP. Entender como usar essas propriedades de cabeçalho de forma eficaz pode ajudá-lo a garantir que seu aplicativo se comunique de forma eficiente e segura com os serviços Web.

Configurar cabeçalhos das solicitações

Os cabeçalhos de solicitação são usados para fornecer informações adicionais ao servidor sobre a solicitação que está sendo feita. Casos de uso comuns incluem especificar o tipo de conteúdo, definir tokens de autenticação e adicionar cabeçalhos personalizados. Você pode definir cabeçalhos de solicitação usando a propriedade DefaultRequestHeaders de HttpClient ou a propriedade de cabeçalhos de HttpRequestMessage.

Exemplo: Definir os cabeçalhos de solicitação personalizados

Definindo cabeçalhos de solicitação personalizados padrão em HttpClient

var client = new HttpClient();
client.DefaultRequestHeaders.Add("Custom-Header", "value");

Definindo cabeçalhos de solicitação personalizados em HttpRequestMessage

var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("Custom-Header", "value");

Exemplo: Definir os cabeçalhos de solicitação comuns

Ao trabalhar com HttpRequestMessage no .NET, a configuração de cabeçalhos de solicitação comuns é essencial para fornecer informações adicionais ao servidor sobre a solicitação que está sendo feita. Esses cabeçalhos podem incluir tokens de autenticação e muito mais. A configuração adequada desses cabeçalhos garante que suas solicitações HTTP sejam processadas corretamente pelo servidor. Para ver uma lista abrangente das propriedades comuns disponíveis no HttpRequestHeaders, consulte Propriedades.

Para definir cabeçalhos de solicitação comuns no HttpRequestMessage, você pode usar a propriedade Headers do objeto HttpRequestMessage. Essa propriedade fornece acesso à coleção HttpRequestHeaders, onde você pode adicionar ou modificar cabeçalhos conforme necessário.

Definindo cabeçalhos de solicitação padrão comuns em HttpClient

using System.Net.Http.Headers;

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

Definindo cabeçalhos de solicitação comuns em HttpRequestMessage

using System.Net.Http.Headers;

var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "token");

Exemplo: Definir cabeçalhos de conteúdo

Os cabeçalhos de conteúdo são usados para fornecer informações adicionais sobre o corpo de uma solicitação ou resposta HTTP. Ao trabalhar com HttpClient no .NET, você pode definir cabeçalhos de conteúdo para especificar o tipo de mídia, a codificação e outros metadados relacionados ao conteúdo que está sendo enviado ou recebido. A configuração adequada de cabeçalhos de conteúdo garante que o servidor e o cliente possam interpretar e processar corretamente o conteúdo.

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, uri);

// Create the content and set the content headers
var jsonData = "{\"key\":\"value\"}";
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");

// The following headers are set automatically by `StringContent`. If you wish to override their values, you can do it like so:
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json; charset=utf-8");
// content.Headers.ContentLength = Encoding.UTF8.GetByteCount(jsonData);

// Assign the content to the request
request.Content = content;

using var response = await client.SendAsync(request);

Exemplo: Definir MaximumErrorResponseLength em HttpClient

O uso de MaximumErrorResponseLength permite que os desenvolvedores especifiquem o comprimento máximo do conteúdo da resposta de erro que o manipulador armazenará em buffer. Isso é útil para controlar a quantidade de dados que são lidos e armazenados na memória quando uma resposta de erro é recebida do servidor. Usando essa técnica, você pode evitar o uso excessivo de memória e melhorar o desempenho do aplicativo ao lidar com respostas de erro grandes.

Existem algumas maneiras de fazer isso, examinaremos a técnica TruncatedReadStream neste exemplo:

internal sealed class TruncatedReadStream(Stream innerStream, long maxSize) : Stream
{
    private long _maxRemainingLength = maxSize;
    public override bool CanRead => true;
    public override bool CanSeek => false;
    public override bool CanWrite => false;

    public override long Length => throw new NotSupportedException();
    public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

    public override void Flush() => throw new NotSupportedException();

    public override int Read(byte[] buffer, int offset, int count)
    {
        return Read(new Span<byte>(buffer, offset, count));
    }

    public override int Read(Span<byte> buffer)
    {
        int readBytes = innerStream.Read(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)));
        _maxRemainingLength -= readBytes;
        return readBytes;
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
    }

    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
    {
        int readBytes = await innerStream.ReadAsync(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)), cancellationToken)
            .ConfigureAwait(false);
        _maxRemainingLength -= readBytes;
        return readBytes;
    }

    public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();

    public override ValueTask DisposeAsync() => innerStream.DisposeAsync();

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            innerStream.Dispose();
        }
    }
}

E exemplo de uso de TruncatedReadStream:

int maxErrorResponseLength = 1 * 1024; // 1 KB

HttpClient client = new HttpClient();
using HttpResponseMessage response = await client.GetAsync(Uri);

if (response.Content is not null)
{
    Stream responseReadStream = await response.Content.ReadAsStreamAsync();
    // If MaxErrorResponseLength is set and the response status code is an error code, then wrap the response stream in a TruncatedReadStream
    if (maxErrorResponseLength >= 0 && !response.IsSuccessStatusCode)
    {
        responseReadStream = new TruncatedReadStream(responseReadStream, maxErrorResponseLength);
    }
    // Read the response stream
    Memory<byte> buffer = new byte[1024];
    int readValue = await responseReadStream.ReadAsync(buffer);
}

Exemplo: Aplicar cabeçalhos de CachePolicy

Aviso

O HttpClient não tem lógica interna para armazenar respostas em cache. Não há solução alternativa além de implementar todo o cache por conta própria. Simplesmente definir os cabeçalhos não alcançará o cache.

Ao migrar de HttpWebRequest para HttpClient, é importante lidar corretamente com cabeçalhos relacionados ao cache, como pragma e cache-control. Esses cabeçalhos controlam como as respostas são armazenadas em cache e recuperadas, garantindo que seu aplicativo se comporte conforme o esperado em termos de desempenho e atualização de dados.

No HttpWebRequest, você pode ter usado a propriedade CachePolicy para definir esses cabeçalhos. No entanto, no HttpClient, você precisa definir manualmente esses cabeçalhos na solicitação.

Código antigo usando HttpWebRequest:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

Na API HttpWebRequest mais antiga, a aplicação de CachePolicy era simples devido ao suporte integrado para esse recurso. No entanto, a API HttpClient mais recente não fornece a mesma funcionalidade interna. Apesar disso, você pode obter um comportamento semelhante implementando um AddCacheControlHeaders que adiciona manualmente cabeçalhos relacionados ao cache.

Novo código usando HttpClient:

public static class CachePolicy
{
    public static void AddCacheControlHeaders(HttpRequestMessage request, RequestCachePolicy policy)

Você pode encontrar a implementação de AddCacheControlHeaders aqui.

AddCacheControlHeaders Uso:

static async Task AddCacheControlHeaders()
{
    HttpClient client = new HttpClient();
    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, Uri);
    CachePolicy.AddCacheControlHeaders(requestMessage, new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore));
    HttpResponseMessage response = await client.SendAsync(requestMessage);
}

Uso de propriedades de buffer

Ao migrar de HttpWebRequest para HttpClient, é importante entender as diferenças em como essas duas APIs lidam com o buffer.

Código antigo usando HttpWebRequest:

No HttpWebRequest, você tem controle direto sobre as propriedades de buffer por meio das propriedades AllowWriteStreamBuffering e AllowReadStreamBuffering. Essas propriedades habilitam ou desabilitam o buffer de dados enviados e recebidos do servidor.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.AllowReadStreamBuffering = true; // Default is `false`.
request.AllowWriteStreamBuffering = false; // Default is `true`.

Novo código usando HttpClient:

Em HttpClient, não há equivalentes diretos para as propriedades AllowWriteStreamBuffering e AllowReadStreamBuffering.

HttpClient não armazena corpos de solicitação em buffer por conta própria, em vez disso, delega a responsabilidade ao HttpContent usado. Conteúdos como StringContent ou ByteArrayContent são logicamente já armazenados em buffer na memória, enquanto o uso de StreamContent não incorrerá em nenhum buffer por padrão. Para forçar o conteúdo a ser armazenado em buffer, você pode chamar HttpContent.LoadIntoBufferAsync antes de enviar a solicitação. Veja um exemplo:

HttpClient client = new HttpClient();

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StreamContent(yourStream);
await request.Content.LoadIntoBufferAsync();

HttpResponseMessage response = await client.SendAsync(request);

Em HttpClient o buffer de leitura é habilitado por padrão. Para evitá-lo, você pode especificar o sinalizador HttpCompletionOption.ResponseHeadersRead ou usar o auxiliar GetStreamAsync.

HttpClient client = new HttpClient();

using HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
await using Stream responseStream = await response.Content.ReadAsStreamAsync();

// Or simply
await using Stream responseStream = await client.GetStreamAsync(uri);