Geonode logo
Geonode Team

Geonode Team

Mis à jour : 16 octobre 2026

Publié : 16 octobre 2026

Configuration du proxy HttpClient en C# : identifiants, délais d'expiration et vérification

Ce guide explique comment configurer un proxy HttpClient en C# sous .NET 8 : un `WebProxy` avec un `NetworkCredential`, le choix entre `HttpClientHandler` et `SocketsHttpHandler`, la réutilisation correcte du client avec `IHttpClientFactory`, les délais d'expiration et l'annulation, la configuration via les variables d'environnement, les listes de contournement, ainsi que les deux exceptions que vous rencontrerez concrètement en production. Il se termine par un programme console complet qui ouvre une session résidentielle persistante, vérifie l’adresse IP de sortie et, en cas d’échec, effectue une nouvelle tentative avec une nouvelle session. Tout ce qui suit cible .NET 8 avec des instructions de niveau supérieur, mais l’API est stable depuis .NET Core 3.0 et le même code se compile sous .NET 6 et 7.

Ce guide explique comment configurer un proxy HttpClient en C# sous .NET 8 : un WebProxy avec un NetworkCredential, le choix entre HttpClientHandler et SocketsHttpHandler, la réutilisation correcte du client avec IHttpClientFactory, les délais d'expiration et l'annulation, la configuration via les variables d'environnement, les listes de contournement, ainsi que les deux exceptions que vous rencontrerez concrètement en production. Il se termine par un programme console complet qui ouvre une session résidentielle persistante, vérifie l’adresse IP de sortie et, en cas d’échec, effectue une nouvelle tentative avec une nouvelle session.

Tout ce qui suit cible .NET 8 avec des instructions de niveau supérieur, mais l’API est stable depuis .NET Core 3.0 et le même code se compile sous .NET 6 et 7.

WebProxy et NetworkCredential

Le composant de base est System.Net.WebProxy, documenté sur learn.microsoft.com. Il contient l’adresse du proxy et, via sa propriété Credentials, le nom d’utilisateur et le mot de passe que le gestionnaire envoie lorsque le proxy émet une requête d’authentification avec le code d’état 407.

using System.Net;

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

Le schéma d’adresse est http://, même lorsque les cibles utilisent le protocole HTTPS. Pour une URL de type https://, le gestionnaire envoie une requête CONNECT au proxy et exécute TLS avec la cible à l’intérieur de ce tunnel ; ainsi, le saut via le proxy reste en HTTP pur et la validation du certificat s’applique à la cible exactement comme ce serait le cas sans proxy.

Comme les identifiants se trouvent dans un NetworkCredential plutôt que dans l’URL, il n’est pas nécessaire d’encoder en pourcentage les caractères spéciaux du mot de passe. C’est là un avantage pratique par rapport aux clients qui n’acceptent les informations d’utilisateur que dans l’URL du proxy.

HttpClientHandler et SocketsHttpHandler

Deux classes de gestionnaires acceptent un proxy. HttpClientHandler est l'API historique ; depuis .NET Core 2.1, elle délègue à SocketsHttpHandler sur toutes les plateformes, il n'y a donc aucune différence fonctionnelle en matière de prise en charge des proxys. La raison d’utiliser directement SocketsHttpHandler réside dans les paramètres supplémentaires qu’elle expose, dont l’un revêt une importance capitale pour les proxys.

// 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 est ce paramètre. HttpClient met les connexions en pool et les réutilise d’une requête à l’autre. Avec un proxy rotatif sur le port 9000, l’adresse IP de sortie est choisie lors de l’établissement de la connexion au proxy ; les requêtes suivantes utilisant la même connexion mise en pool peuvent conserver la même adresse de sortie. Le fait de limiter la durée de vie des connexions mises en pool impose une reconnexion périodique, ce qui permet également de prendre en compte les changements DNS concernant l’hôte du proxy. Deux minutes constituent une valeur par défaut raisonnable ; réduisez-la si vous souhaitez que la rotation ait lieu plus souvent que ne le permet le renouvellement naturel de vos connexions.

Le tableau ci-dessous résume les cas dans lesquels chaque option est appropriée.

BesoinGestionnaireParamètre
Configuration minimale du proxy, code existantHttpClientHandlerProxy, UseProxy = true
Réutilisation limitée des connexions avec rotation des adresses IPSocketsHttpHandlerPooledConnectionLifetime
Limiter le nombre de connexions par hôte proxySocketsHttpHandlerMaxConnectionsPerServer
Désactiver le proxy pour un clientl'un ou l'autreUseProxy = false

Réutiliser un seul HttpClient

HttpClient est conçu pour être créé une seule fois et réutilisé pendant toute la durée de vie de l'application. La création d'une nouvelle instance à chaque requête épuise les sockets en cas de charge élevée et, avec un proxy, cela entraîne également la perte des tunnels CONNECT mis en pool que vous venez de mettre du temps à établir. Dans une application console ou un worker, conservez une seule instance statique.

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

IHttpClientFactory dans ASP.NET Core

Dans ASP.NET Core, enregistrez un client nommé et associez-lui le gestionnaire via ConfigurePrimaryHttpMessageHandler. L'usine gère pour vous la durée de vie des gestionnaires et les fait tourner selon un calendrier défini, ce qui évite à la fois le problème d'épuisement des sockets et les DNS obsolètes.

// 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();

Injectez IHttpClientFactory partout où vous avez besoin du client et appelez CreateClient("geonode"). La création de l’objet HttpClient renvoyé est peu coûteuse, car le gestionnaire sous-jacent, plus coûteux, est partagé.

Délais d'expiration et CancellationToken

HttpClient.Timeout correspond au temps total alloué à une requête, incluant la connexion au proxy, la négociation CONNECT, le protocole TLS avec la cible et la lecture des en-têtes de réponse. La valeur par défaut est de 100 secondes, ce qui est trop long pour un worker de scraping. Définissez-la côté client et, pour un contrôle plus fin, transmettez un CancellationToken par requête.

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

Les sorties résidentielles ajoutent de la latence par rapport à une connexion directe ; prévoyez donc quelques secondes supplémentaires par requête. Un délai d’expiration constant au même stade sur de nombreuses requêtes indique généralement un problème de chemin réseau vers l’hôte proxy, et non une cible lente.

DefaultProxy et variables d'environnement

Si vous ne définissez pas Proxy sur le gestionnaire, .NET utilise HttpClient.DefaultProxy. Sous .NET Core 3.0 et versions ultérieures, cette propriété statique est initialisée à partir des variables d’environnement HTTP_PROXY, HTTPS_PROXY et NO_PROXY, et l’URL peut contenir des identifiants.

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"

Vous pouvez également attribuer la valeur HttpClient.DefaultProxy dans le code au démarrage, ce qui affecte tous les gestionnaires qui ne redéfinissent pas Proxy. C’est pratique dans les conteneurs, mais il s’agit d’un état global : un client de contrôle d’intégrité ou un appel à une API interne passera également par le proxy résidentiel, à moins que NO_PROXY ne répertorie ces hôtes. Pour tout ce qui va au-delà d’un simple script jetable, il est plus clair d’utiliser explicitement WebProxy sur un gestionnaire nommé.

BypassList et BypassProxyOnLocal

WebProxy dispose de son propre mécanisme d’exclusion. BypassProxyOnLocal = true contourne le proxy pour les hôtes dont le nom ne contient pas de point (tels que localhost ou un simple nom de machine). BypassList accepte les expressions régulières correspondant à l’URI cible.

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

N’oubliez pas qu’il s’agit de motifs d’expressions régulières, et non de motifs glob ; un point non échappé correspond à n’importe quel caractère.

Gestion des erreurs 407 et autres

Deux types d'erreurs représentent la quasi-totalité des tickets liés au proxy HttpClient. Le premier est l'erreur 407 « Proxy Authentication Required », qui signifie que le proxy a rejeté les identifiants avant de transmettre quoi que ce soit. Elle se présente généralement sous la forme d’un code HttpRequestException, mais selon la version de .NET et le point d’échec, vous pouvez également obtenir une réponse dont le code d’état est HttpStatusCode.ProxyAuthenticationRequired. Vérifiez les deux.

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

Le second est le code TaskCanceledException, correspondant à un délai d’expiration, comme indiqué ci-dessus. Règle importante pour les deux cas : un code 407 correspond à une erreur de configuration et ne doit pas faire l’objet d’une nouvelle tentative ; un délai d’expiration ou un code 5xx provenant de la cible est temporaire et justifie une nouvelle tentative dans une nouvelle session.

Sessions persistantes via le nom d'utilisateur

Le port 9000 attribue une nouvelle adresse IP de sortie à chaque nouvelle connexion. Lorsqu'un flux nécessite une seule adresse IP pour plusieurs requêtes (connexion suivie de pages de données ou de résultats paginés), utilisez le port 10000 et sélectionnez la session via le nom d'utilisateur. La grammaire ajoute des suffixes à votre nom d’utilisateur de base : -country-xx pour le ciblage géographique, -session-XXXXXXXX avec un identifiant alphanumérique de 8 caractères, et éventuellement -lifetime-SECONDS compris entre 180 et 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)
    };
}

Chaque requête effectuée via un client s'appuyant sur ce proxy partage la même adresse IP de sortie pendant dix minutes. Pour changer d'adresse IP, créez un nouveau gestionnaire avec un nouvel identifiant de session. La session étant liée aux identifiants d'authentification, deux clients portant le même nom d'utilisateur partagent la même adresse IP de sortie, même entre différents processus.

Vérification de l'adresse IP de sortie

Ne partez jamais du principe que le proxy HttpClient est activé ; vérifiez-le. Envoyez la requête https://api.ipify.org?format=json (ou https://httpbin.org/ip) via le client et comparez-la à votre adresse réelle. Avec une session persistante, les deux appels renvoient la même adresse IP ; avec le port 9000 et une nouvelle connexion, elles diffèrent.

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

Exemple complet en mode console (.NET 8)

Le programme ci-dessous rassemble tous ces éléments : une session persistante, des délais d'expiration limités, la détection du code d'erreur 407 et jusqu'à trois tentatives avec une nouvelle session en cas d'échecs temporaires. Créez-le avec dotnet new console -n ProxyDemo, remplacez Program.cs, définissez GEONODE_USER et GEONODE_PASS, puis exécutez 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");

Le gestionnaire est recréé à chaque tentative de manière intentionnelle : une session persistante est liée au nom d’utilisateur figurant dans les identifiants ; une nouvelle session nécessite donc un nouveau WebProxy. Dans un service fonctionnant en continu, vous mettriez en cache un client par identifiant de session plutôt que par requête.

Erreurs courantes

Création d'un HttpClient par requête

Chaque nouveau client ouvre un nouveau tunnel CONNECT et laisse des sockets dans TIME_WAIT. Réutilisez une seule instance, ou utilisez IHttpClientFactory, et laissez PooledConnectionLifetime se charger de la rotation.

Utilisation d'une adresse proxy de type « https:// »

Le saut de proxy s'effectue en HTTP simple ; les cibles HTTPS transitent à l'intérieur du tunnel CONNECT. Un schéma https:// sur l'adresse WebProxy indique à .NET de négocier le protocole TLS avec le proxy lui-même, ce qui échoue face à un point de terminaison qui attend du HTTP simple.

Réessayer après un code 407

Un code 407 signifie que le proxy n’a pas accepté les identifiants. Réessayer avec le même nom d’utilisateur et le même mot de passe ne fait que perdre du temps. Échouez rapidement et vérifiez la syntaxe du nom d’utilisateur, en particulier la longueur de l’identifiant de session et la plage de durée de vie.

Ignorer NO_PROXY et BypassList

Lorsque le proxy provient de DefaultProxy, tous les appels sortants du processus l’utilisent, y compris les contrôles d’intégrité et les API internes. Répertoriez ces hôtes dans NO_PROXY ou BypassList, ou configurez le proxy uniquement sur un client nommé.

Étape suivante

Le code ci-dessus fonctionne tel quel avec les proxys résidentiels Geonode : 2,5 millions d’adresses IP en ligne dans plus de 195 pays, HTTP, HTTPS et SOCKS5 sur un seul hôte, ainsi que des sessions persistantes pouvant aller jusqu’à 24 heures, sélectionnées via la grammaire de nom d’utilisateur présentée ici. Le ciblage par pays, région, ville et ASN utilise les mêmes suffixes ; ainsi, affiner l’emplacement de sortie ne nécessite qu’une modification de la chaîne de caractères, et non une modification du code. La tarification correspond à l’offre pay-as-you-go sans engagement, à partir de 10 Go pour 7,92 $ par mois, avec un essai de 3 jours à 5 $. Consultez la page proxys résidentiels pour plus de détails, ou comparez les formules sur la page des tarifs. Pour la suite du processus, commencez par le cas d'utilisation du web scraping.

FAQ

Comment configurer un proxy HttpClient avec un nom d'utilisateur et un mot de passe en C# ?

Créez un objet WebProxy avec l'adresse du proxy et définissez sa propriété Credentials sur un objet NetworkCredential contenant le nom d'utilisateur et le mot de passe. Attribuez ce WebProxy à la propriété Proxy d’un HttpClientHandler ou d’un SocketsHttpHandler, définissez UseProxy sur true, puis transmettez le gestionnaire au constructeur de HttpClient. Le gestionnaire envoie les identifiants lorsque le proxy renvoie un code d'état 407.

Dois-je utiliser HttpClientHandler ou SocketsHttpHandler pour un proxy ?

Sous .NET Core 2.1 et versions ultérieures, HttpClientHandler est un simple wrapper autour de SocketsHttpHandler ; les deux fonctionnent donc. SocketsHttpHandler expose PooledConnectionLifetime, ce qui vous permet de recycler périodiquement les connexions mises en pool. Cela a son importance avec les proxys à rotation, car une connexion réutilisée conserve la même adresse IP de sortie jusqu’à sa fermeture. Privilégiez SocketsHttpHandler pour le nouveau code.

Pourquoi ma requête de proxy HttpClient lève-t-elle une exception TaskCanceledException ?

HttpClient signale un délai d’expiration sous la forme de TaskCanceledException, et non comme une exception de délai d’expiration dédiée. Elle est levée lorsque le délai HttpClient.Timeout est écoulé ou lorsque le CancellationToken que vous avez passé est annulé. Sous .NET 5 et versions ultérieures, l’exception interne est de type TimeoutException lorsque le délai d’expiration du client en est la cause, ce qui vous permet de distinguer les deux cas.

HttpClient lit-il les variables d’environnement HTTP_PROXY et HTTPS_PROXY ?

Oui. Sous .NET Core 3.0 et versions ultérieures, HttpClient.DefaultProxy est initialisé à partir de HTTP_PROXY, HTTPS_PROXY et NO_PROXY, et les gestionnaires l’utilisent lorsqu’aucun Proxy explicite n’est défini. Les identifiants peuvent être inclus dans l’URL. La configuration des variables d’environnement est pratique dans les conteneurs, mais un WebProxy explicite dans le code est plus facile à comprendre.

Comment conserver la même adresse IP sur plusieurs requêtes proxy HttpClient ?

Utilisez le port fixe 10000 et ajoutez un suffixe de session au nom d’utilisateur, par exemple USER-country-us-session-a1b2c3d4-lifetime-600. Toutes les requêtes envoyées avec ce nom d’utilisateur partagent une même adresse IP de sortie pendant la durée spécifiée en secondes, comprise entre 180 et 86 400. Générez un nouvel identifiant de session de 8 caractères lorsque vous souhaitez une nouvelle adresse IP.

Quelles sont les limites applicables lors du scraping via un proxy HttpClient résidentiel ?

La bande passante est facturée au gigaoctet ; ne téléchargez donc que ce dont vous avez besoin et évitez les images dans la mesure du possible. Les sessions persistantes conservent une adresse IP pendant 24 heures au maximum et peuvent prendre fin plus tôt si l’appareil se déconnecte ; votre code doit donc gérer un changement d’adresse IP. Un proxy ne vous dispense pas de respecter les conditions d’utilisation d’un site, les directives relatives aux robots ou les limites de débit.