Geonode logo
Geonode Team

Geonode Team

Atualizado: 16 de outubro de 2026

Publicado: 16 de outubro de 2026

Configuração do proxy do HttpClient em C#: credenciais, tempos de espera e verificação

Este guia mostra como configurar um proxy HttpClient em C# no .NET 8: um `WebProxy` com `NetworkCredential`, a escolha entre `HttpClientHandler` e `SocketsHttpHandler`, a reutilização correta do cliente com `IHttpClientFactory`, tempos de espera e cancelamento, configuração de variáveis de ambiente, listas de exclusão e as duas exceções com que se irá de facto deparar em produção. Termina com um programa de consola completo que abre uma sessão residencial permanente, verifica o IP de saída e, em caso de falha, tenta novamente com uma nova sessão. Tudo o que se segue destina-se ao .NET 8 com instruções de nível superior, mas a API tem-se mantido estável desde o .NET Core 3.0 e o mesmo código compila no .NET 6 e 7.

Este guia mostra como configurar um proxy HttpClient em C# no .NET 8: um WebProxy com NetworkCredential, a escolha entre HttpClientHandler e SocketsHttpHandler, a reutilização correta do cliente com IHttpClientFactory, tempos de espera e cancelamento, configuração de variáveis de ambiente, listas de exclusão e as duas exceções com que se irá de facto deparar em produção. Termina com um programa de consola completo que abre uma sessão residencial permanente, verifica o IP de saída e, em caso de falha, tenta novamente com uma nova sessão.

Tudo o que se segue destina-se ao .NET 8 com instruções de nível superior, mas a API tem-se mantido estável desde o .NET Core 3.0 e o mesmo código compila no .NET 6 e 7.

WebProxy e NetworkCredential

O componente básico é o System.Net.WebProxy, documentado em learn.microsoft.com. Este contém o endereço do proxy e, através da sua propriedade Credentials, o nome de utilizador e a palavra-passe que o manipulador envia quando o proxy solicita autenticação com o código de estado 407.

using System.Net;

var proxy = new WebProxy("http://premium-residential.geonode.com:9000")
{
    Credentials = new NetworkCredential("USERNAME", "PASSWORD")
};

O esquema de endereço é http://, mesmo quando os destinos são HTTPS. Para um URL https://, o manipulador envia um pedido CONNECT ao proxy e executa o TLS com o destino dentro desse túnel, pelo que o próprio salto do proxy permanece em HTTP simples e a validação do certificado aplica-se ao destino exatamente como aconteceria sem um proxy.

Como as credenciais residem num NetworkCredential em vez de na URL, não é necessário codificar com o símbolo de por cento os caracteres especiais na palavra-passe. Essa é uma vantagem prática em relação aos clientes que apenas aceitam informações de utilizador na URL do proxy.

HttpClientHandler versus SocketsHttpHandler

Duas classes de handler aceitam um proxy. HttpClientHandler é a API tradicional; desde o .NET Core 2.1 que delega a SocketsHttpHandler em todas as plataformas, pelo que não existe qualquer diferença funcional no que diz respeito ao suporte a proxies. A razão para utilizar diretamente a SocketsHttpHandler reside nos controlos adicionais que esta expõe, sendo que um deles é de grande importância para os proxies.

// Option A: HttpClientHandler
var handlerA = new HttpClientHandler
{
    Proxy = proxy,
    UseProxy = true
};

// Option B: SocketsHttpHandler (preferred for new code)
var handlerB = new SocketsHttpHandler
{
    Proxy = proxy,
    UseProxy = true,
    PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};

PooledConnectionLifetime é essa opção. HttpClient agrupa as ligações e reutiliza-as entre pedidos. Com um proxy rotativo na porta 9000, o IP de saída é escolhido quando é estabelecida uma ligação ao proxy; os pedidos subsequentes na mesma ligação agrupada podem manter o mesmo IP de saída. Atribuir às ligações agrupadas um tempo de vida limitado força a reconexão periódica, o que também deteta alterações no DNS do anfitrião do proxy. Dois minutos é um valor predefinido razoável; reduza-o se pretender que a rotação ocorra com mais frequência do que a rotatividade natural das suas ligações permite.

A tabela resume quando cada opção é adequada.

NecessidadeGestorConfiguração
Configuração mínima do proxy, código existenteHttpClientHandlerProxy, UseProxy = true
Reutilização limitada de ligações com IPs rotativosSocketsHttpHandlerPooledConnectionLifetime
Limitar as ligações por anfitrião de proxySocketsHttpHandlerMaxConnectionsPerServer
Desativar o proxy para um clientequalquer umUseProxy = false

Reutilizar um HttpClient

O HttpClient foi concebido para ser criado uma única vez e reutilizado durante todo o ciclo de vida da aplicação. Criar uma nova instância por cada pedido esgota os sockets sob carga e, com um proxy, também desperdiça os túneis CONNECT em pool que acabou de demorar a estabelecer. Numa aplicação de consola ou num worker, mantenha uma única instância estática.

var http = new HttpClient(handlerB)
{
    Timeout = TimeSpan.FromSeconds(30)
};

IHttpClientFactory no ASP.NET Core

No ASP.NET Core, registe um cliente com nome e associe o manipulador através de ConfigurePrimaryHttpMessageHandler. A fábrica gere o ciclo de vida do manipulador por si e alterna os manipuladores de acordo com uma programação, o que evita tanto o problema de esgotamento dos sockets como o DNS desatualizado.

// Program.cs
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("geonode", client =>
{
    client.Timeout = TimeSpan.FromSeconds(30);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
    var proxy = new WebProxy("http://premium-residential.geonode.com:9000")
    {
        Credentials = new NetworkCredential(
            Environment.GetEnvironmentVariable("GEONODE_USER"),
            Environment.GetEnvironmentVariable("GEONODE_PASS"))
    };

    return new SocketsHttpHandler
    {
        Proxy = proxy,
        UseProxy = true,
        PooledConnectionLifetime = TimeSpan.FromMinutes(2)
    };
});

var app = builder.Build();

app.MapGet("/ip", async (IHttpClientFactory factory) =>
{
    var client = factory.CreateClient("geonode");
    return await client.GetStringAsync("https://api.ipify.org?format=json");
});

app.Run();

Injete o IHttpClientFactory sempre que precisar do cliente e chame o CreateClient("geonode"). O HttpClient devolvido é barato de criar, porque o manipulador subjacente, que é dispendioso, é partilhado.

Tempos limite e CancellationToken

HttpClient.Timeout é o tempo total permitido para um pedido, incluindo a ligação ao proxy, o handshake CONNECT, o TLS com o destino e a leitura dos cabeçalhos da resposta. O valor predefinido é de 100 segundos, o que é demasiado tempo para um trabalhador de scraper. Defina-o no cliente e, para um controlo mais preciso, passe um CancellationToken por pedido.

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));

try
{
    var body = await http.GetStringAsync("https://example.com/", cts.Token);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    // HttpClient.Timeout elapsed
}
catch (TaskCanceledException)
{
    // the CancellationToken was cancelled (our 15 s budget)
}

As ligações residenciais aumentam a latência em comparação com uma ligação direta, pelo que deve prever alguns segundos adicionais por pedido. Um tempo limite consistente na mesma fase em muitos pedidos indica normalmente um problema no caminho de rede até ao servidor proxy, e não um destino lento.

DefaultProxy e variáveis de ambiente

Se não definir Proxy no manipulador, o .NET utiliza HttpClient.DefaultProxy. No .NET Core 3.0 e versões posteriores, essa propriedade estática é inicializada a partir das variáveis de ambiente HTTP_PROXY, HTTPS_PROXY e NO_PROXY, e o URL pode conter credenciais.

export HTTP_PROXY="http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
export HTTPS_PROXY="http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
export NO_PROXY="localhost,127.0.0.1,.internal"

Também é possível atribuir o valor a HttpClient.DefaultProxy no código aquando do arranque, o que afeta todos os manipuladores que não substituam o valor de Proxy. Isto é útil em contentores, mas trata-se de um estado global: um cliente de verificação de integridade ou uma chamada a uma API interna também passará pelo proxy residencial, a menos que o NO_PROXY inclua esses anfitriões na lista. Para qualquer coisa que vá além de um script descartável, um WebProxy explícito num manipulador nomeado é mais claro.

BypassList e BypassProxyOnLocal

O WebProxy tem o seu próprio mecanismo de exclusão. O BypassProxyOnLocal = true ignora o proxy para anfitriões sem um ponto no nome (como o localhost ou um nome de máquina simples). O BypassList aceita expressões regulares que correspondam ao URI de destino.

var proxy = new WebProxy("http://premium-residential.geonode.com:9000")
{
    Credentials = new NetworkCredential("USERNAME", "PASSWORD"),
    BypassProxyOnLocal = true,
    BypassList = new[] { @"^https?://(.*\.)?internal\.example(:\d+)?/" }
};

Lembre-se de que estes são padrões de expressões regulares, não padrões glob; um ponto não escapado corresponde a qualquer carácter.

Tratamento do erro 407 e de outras falhas

Dois tipos de falha são responsáveis por quase todos os erros relacionados com o proxy do HttpClient. O primeiro é o erro 407 «Proxy Authentication Required», o que significa que o proxy rejeitou as credenciais antes de reencaminhar qualquer coisa. Normalmente, surge como um HttpRequestException e, dependendo da versão do .NET e do ponto de falha, poderá, em vez disso, receber uma resposta cujo código de estado seja HttpStatusCode.ProxyAuthenticationRequired. Verifique ambos.

using System.Net;
using System.Net.Http;

try
{
    using var response = await http.GetAsync("https://example.com/");

    if (response.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
    {
        throw new InvalidOperationException("proxy rejected credentials (407)");
    }

    response.EnsureSuccessStatusCode();
    var html = await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
{
    // Also 407: fix the username grammar or password; retrying will not help.
}
catch (HttpRequestException ex)
{
    // Connection refused, DNS failure, TLS error, or EnsureSuccessStatusCode on 4xx/5xx.
    Console.Error.WriteLine($"request failed: {ex.Message} (status {ex.StatusCode})");
}

O segundo é o código TaskCanceledException, relativo a tempos de espera esgotados, já abordado acima. A regra importante para ambos: um código 407 é um erro de configuração e não deve ser repetido; um tempo de espera esgotado ou um código 5xx proveniente do destino é transitório e constitui um candidato razoável para uma nova tentativa numa nova sessão.

Sessões persistentes através do nome de utilizador

A porta 9000 atribui um novo endereço IP de saída para cada nova ligação. Quando um fluxo necessita de um único endereço IP para vários pedidos (início de sessão seguido de páginas de dados ou resultados paginados), utilize a porta 10000 e selecione a sessão através do nome de utilizador. A gramática acrescenta sufixos ao seu nome de utilizador base: -country-xx para segmentação geográfica, -session-XXXXXXXX com um identificador alfanumérico de 8 caracteres e, opcionalmente, -lifetime-SECONDS de 180 até 86 400.

static string NewSessionId() =>
    Guid.NewGuid().ToString("N")[..8]; // 8 alphanumeric chars

static WebProxy StickyProxy(string baseUser, string pass, string country = "us", int lifetime = 600)
{
    var user = $"{baseUser}-country-{country}-session-{NewSessionId()}-lifetime-{lifetime}";
    return new WebProxy("http://premium-residential.geonode.com:10000")
    {
        Credentials = new NetworkCredential(user, pass)
    };
}

Cada pedido efetuado através de um cliente construído com base neste proxy partilha o mesmo IP de saída durante dez minutos. Para mudar de IP, crie um novo manipulador com um novo ID de sessão. Uma vez que a sessão está associada às credenciais, dois clientes com o mesmo nome de utilizador partilham o mesmo IP de saída, mesmo entre processos.

Verificação do IP de saída

Nunca presuma que o proxy do HttpClient está a ser utilizado; confirme isso. Envie o pedido https://api.ipify.org?format=json (ou https://httpbin.org/ip) através do cliente e compare com o seu endereço real. Com uma sessão persistente, duas chamadas devolvem o mesmo IP; com a porta 9000 e uma ligação nova, os resultados diferem.

using System.Text.Json;

var json = await http.GetStringAsync("https://api.ipify.org?format=json");
var ip = JsonDocument.Parse(json).RootElement.GetProperty("ip").GetString();
Console.WriteLine($"exit IP: {ip}");

Exemplo completo na consola (.NET 8)

O programa abaixo reúne todos os elementos: uma sessão permanente, tempos de espera limitados, deteção do erro 407 e até três tentativas com uma nova sessão em caso de falhas transitórias. Crie-o com dotnet new console -n ProxyDemo, substitua Program.cs, defina GEONODE_USER e GEONODE_PASS e execute dotnet run.

// Program.cs  (.NET 8, top-level statements)
using System.Net;
using System.Text.Json;

var baseUser = Environment.GetEnvironmentVariable("GEONODE_USER")
    ?? throw new InvalidOperationException("GEONODE_USER not set");
var password = Environment.GetEnvironmentVariable("GEONODE_PASS")
    ?? throw new InvalidOperationException("GEONODE_PASS not set");

var target = args.Length > 0 ? args[0] : "https://api.ipify.org?format=json";

for (var attempt = 1; attempt <= 3; attempt++)
{
    var sessionId = Guid.NewGuid().ToString("N")[..8];
    var user = $"{baseUser}-country-us-session-{sessionId}-lifetime-600";

    var handler = new SocketsHttpHandler
    {
        Proxy = new WebProxy("http://premium-residential.geonode.com:10000")
        {
            Credentials = new NetworkCredential(user, password)
        },
        UseProxy = true,
        PooledConnectionLifetime = TimeSpan.FromMinutes(2)
    };

    using var http = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));

    try
    {
        // 1. Confirm the exit IP for this session.
        var ipJson = await http.GetStringAsync("https://api.ipify.org?format=json", cts.Token);
        var ip = JsonDocument.Parse(ipJson).RootElement.GetProperty("ip").GetString();
        Console.WriteLine($"[attempt {attempt}] session {sessionId} exit IP {ip}");

        // 2. Fetch the real target on the same IP.
        using var response = await http.GetAsync(target, cts.Token);

        if (response.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
        {
            Console.Error.WriteLine("407: proxy rejected credentials, not retrying");
            return;
        }

        if ((int)response.StatusCode is 403 or 429 or >= 500)
        {
            Console.Error.WriteLine($"[attempt {attempt}] got {(int)response.StatusCode}, rotating session");
            continue;
        }

        var body = await response.Content.ReadAsStringAsync(cts.Token);
        Console.WriteLine($"{(int)response.StatusCode} {target} ({body.Length} chars)");
        return;
    }
    catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
    {
        Console.Error.WriteLine("407: proxy rejected credentials, not retrying");
        return;
    }
    catch (HttpRequestException ex)
    {
        Console.Error.WriteLine($"[attempt {attempt}] network error: {ex.Message}");
    }
    catch (TaskCanceledException)
    {
        Console.Error.WriteLine($"[attempt {attempt}] timed out");
    }

    await Task.Delay(TimeSpan.FromMilliseconds(500 * attempt));
}

Console.Error.WriteLine("gave up after 3 attempts");

O manipulador é recriado propositadamente a cada tentativa: uma sessão persistente está associada ao nome de utilizador nas credenciais, pelo que uma nova sessão requer um novo WebProxy. Num serviço de longa duração, seria preferível armazenar em cache um cliente por ID de sessão, em vez de por pedido.

Erros comuns

Criar um HttpClient por pedido

Cada novo cliente abre um novo túnel CONNECT e deixa sockets no TIME_WAIT. Reutilize uma instância ou utilize o IHttpClientFactory e deixe que o PooledConnectionLifetime trate da rotação.

Utilização de um endereço de proxy https://

O salto do proxy é em HTTP simples; os destinos HTTPS viajam dentro do túnel CONNECT. Um esquema https:// no endereço WebProxy indica ao .NET para negociar TLS com o próprio proxy, o que falha quando o ponto final espera HTTP simples.

Repetir uma tentativa após um erro 407

Um erro 407 significa que o proxy não aceitou as credenciais. Repetir a tentativa com o mesmo nome de utilizador e palavra-passe é apenas uma perda de tempo. Detete rapidamente a falha e verifique a sintaxe do nome de utilizador, especialmente o comprimento do ID de sessão e o intervalo de validade.

Ignorar NO_PROXY e BypassList

Quando o proxy provém de DefaultProxy, todas as chamadas de saída no processo utilizam-no, incluindo verificações de integridade e APIs internas. Liste esses anfitriões em NO_PROXY ou BypassList, ou configure o proxy apenas num cliente específico.

Próximo passo

O código acima funciona sem alterações com os proxies residenciais Geonode: 2,5 milhões de IPs online em mais de 195 países, HTTP, HTTPS e SOCKS5 num único host, e sessões persistentes até 24 horas selecionadas através da sintaxe de nome de utilizador aqui apresentada. A segmentação por país, estado, cidade e ASN utiliza os mesmos sufixos, pelo que restringir a localização de saída implica apenas uma alteração na cadeia de caracteres, e não uma alteração no código. O preço é o do pay-as-you-go sem contrato, a partir de 10 GB por 7,92 $ por mês, com um período de teste de 3 dias por 5 $. Consulte proxies residenciais para mais detalhes ou compare os planos na página de preços. Para o resto do processo, comece pelo caso de utilização de web scraping.

Perguntas frequentes

Como configuro um proxy HttpClient com nome de utilizador e palavra-passe em C#?

Crie um WebProxy com o endereço do proxy e defina a sua propriedade Credentials como um NetworkCredential que contenha o nome de utilizador e a palavra-passe. Atribua esse WebProxy à propriedade Proxy de um HttpClientHandler ou SocketsHttpHandler, defina UseProxy como true e passe o manipulador ao construtor de HttpClient. O manipulador envia as credenciais quando o proxy responde com o código de estado 407.

Devo usar o HttpClientHandler ou o SocketsHttpHandler para um proxy?

No .NET Core 2.1 e versões posteriores, o HttpClientHandler é um invólucro simples sobre o SocketsHttpHandler, pelo que ambos funcionam. O SocketsHttpHandler expõe o PooledConnectionLifetime, o que permite reciclar periodicamente as ligações em pool. Isto é importante no caso de proxies rotativos, porque uma ligação reutilizada mantém o mesmo IP de saída até ser encerrada. Dá preferência ao SocketsHttpHandler para código novo.

Por que razão a minha solicitação de proxy do HttpClient lança uma exceção TaskCanceledException?

O HttpClient reporta um tempo limite como TaskCanceledException, e não como uma exceção de tempo limite específica. É lançada quando o HttpClient.Timeout expira ou quando o CancellationToken que passou é cancelado. No .NET 5 e versões posteriores, a exceção interna é um TimeoutException quando a causa foi o tempo limite do cliente, o que permite distinguir as duas situações.

O HttpClient lê as variáveis de ambiente HTTP_PROXY e HTTPS_PROXY?

Sim. No .NET Core 3.0 e versões posteriores, o HttpClient.DefaultProxy é inicializado a partir de HTTP_PROXY, HTTPS_PROXY e NO_PROXY, e os manipuladores utilizam-no quando não está definido nenhum Proxy explícito. As credenciais podem ser incluídas na URL. Definir as variáveis de ambiente é conveniente em contentores, mas um WebProxy explícito no código é mais fácil de compreender.

Como posso manter o mesmo IP em várias solicitações de proxy do HttpClient?

Utilize a porta fixa 10000 e adicione um sufixo de sessão ao nome de utilizador, por exemplo, USER-country-us-session-a1b2c3d4-lifetime-600. Todas as solicitações enviadas com esse nome de utilizador partilham um único IP de saída durante o período de validade, expresso em segundos, entre 180 e 86 400. Gere um novo ID de sessão de 8 caracteres quando pretender um IP novo.

Que limites se aplicam ao fazer scraping através de um proxy HttpClient residencial?

A largura de banda é cobrada por gigabyte, por isso descarregue apenas o que precisar e evite imagens sempre que possível. As sessões fixas mantêm um IP durante, no máximo, 24 horas e podem terminar mais cedo se o dispositivo ficar offline; por isso, o seu código deve estar preparado para lidar com uma mudança de IP. Um proxy não o isenta dos termos de serviço de um site, das diretivas para robôs ou dos limites de taxa.