Geonode logo
Geonode Team

Geonode Team

Mis à jour : 7 octobre 2026

Publié : 2 septembre 2026

Comment utiliser un proxy avec SuperAgent sous Node.js

SuperAgent ne dispose d'aucune option de proxy intégrée. Vous devez soit ajouter une extension, soit fournir un agent HTTP, mais ces deux approches sont compliquées par un conflit de noms : le paramètre « `.agent()` » de SuperAgent désigne déjà tout autre chose. Ce conflit engendre une confusion particulière : les utilisateurs pensent avoir configuré un proxy alors qu'ils ont en réalité configuré un « cookie jar ». Ce guide aborde ces deux méthodes, la terminologie associée et la vérification qui vous permet de déterminer laquelle est effectivement active.

Nous sommes Geonode et nous vendons des proxys ; ce guide explique donc comment utiliser notre type de produit avec un client spécifique. La ligne à ne pas manquer, même si vous passez le reste : vérifiez l'adresse de sortie après la configuration, car un paramètre de proxy qui ne fonctionne pas ne génère aucune erreur. SuperAgent se fera un plaisir d’envoyer votre requête directement, renverra un code 200 et ne vous donnera aucune indication que le proxy a été contourné. La section consacrée à la vérification ne comporte que quatre lignes et fait toute la différence entre savoir et supposer.

Notez également que SuperAgent fonctionne également dans les navigateurs, où rien de tout cela ne s’applique : on ne peut pas demander à un navigateur d’utiliser un proxy depuis JavaScript, donc tout ce qui est décrit ici concerne uniquement Node.js.

Le problème de terminologie

Clarifions d’abord ce point, car il est source d’erreurs réelles.

Dans SuperAgent, la commande .agent() sans argument crée une copie de SuperAgent qui conserve les cookies. La documentation est claire : « Sous Node, SuperAgent n’enregistre pas les cookies par défaut, mais vous pouvez utiliser la méthode .agent() pour créer une copie de SuperAgent qui enregistre les cookies. Chaque copie dispose d’un espace de stockage de cookies distinct. »

const agent = request.agent();
await agent.post("/login").send({ user, pass });
await agent.get("/cookied-page");   // session cookie carried over

Cet agent comporte également des valeurs par défaut : « Les méthodes de requête standard appelées sur l’agent seront utilisées par défaut pour toutes les requêtes effectuées par cet agent. »

Par ailleurs, la méthode .agent(httpAgent) avec un argument définit l’http.Agent de Node pour la requête, qui gère la prise en charge du proxy.

Même nom de méthode, deux fonctions distinctes, qui ne se distinguent que par la présence ou non d’un argument. Si vous vous êtes renseigné à la fois sur les agents SuperAgent et sur les agents proxy au cours de la même session, il est important de bien comprendre cette distinction avant de commencer à coder.

Première solution : un agent proxy

C'est l'approche à privilégier, pour des raisons de maintenance.

import request from "superagent";
import { HttpsProxyAgent } from "https-proxy-agent";

const agent = new HttpsProxyAgent("http://myuser:mypass@proxy.example.com:9000");

const res = await request
  .get("https://api.example.com/items")
  .agent(agent);

Pour SOCKS, remplacez le paquet par :

import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent("socks5h://proxy.example.com:1080");

Remarque : utilisez socks5h

plutôt que socks5

. La variante h

résout les noms d’hôtes au niveau du proxy plutôt qu’en local, ce qui empêche les requêtes DNS d’être envoyées à votre propre résolveur alors que votre trafic sort ailleurs — une fuite qui annule silencieusement l’intérêt d’utiliser un proxy pour la géolocalisation.

Et proxy-agent

gère quel que soit le protocole spécifié par l’URL, ce qui est utile lorsque le proxy provient d’une configuration :

import { ProxyAgent } from "proxy-agent";
const agent = new ProxyAgent();   // reads http_proxy / https_proxy / no_proxy

Pourquoi ces paquets plutôt que l’extension SuperAgent : tous les trois sont activement maintenus. D’après une vérification effectuée dans le registre npm en septembre 2026, proxy-agent

est à la version 8.0.2, https-proxy-agent

à la version 9.1.0 et socks-proxy-agent

à la version 10.1.0, toutes publiées en juin 2026.

Deuxième solution : superagent-proxy

L'extension spécialement conçue pour cela, et la mise en garde qui l'accompagne.

import request from "superagent";
import superagentProxy from "superagent-proxy";

superagentProxy(request);

const res = await request
  .get("https://api.example.com/items")
  .proxy("http://myuser:mypass@proxy.example.com:9000");

Son fichier README la décrit comme une extension de « la classe Request de superagent avec une fonction .proxy(uri) », et précise qu’« elle s’appuie sur le module proxy-agent ».

L’API est plus pratique que de passer un agent. L’appel .proxy(uri) s’intègre mieux dans une chaîne, et il accepte les URI « HTTP, HTTPS ou SOCKS », en déléguant la sélection du protocole à proxy-agent.

Le bémol concerne la date de sortie. superagent-proxy en est à la version 3.0.0, publiée en septembre 2021 — soit environ cinq ans à la date de rédaction de cet article, alors que sa dépendance sous-jacente proxy-agent a continué d’évoluer. Elle n’est pas obsolète et ne présente pas de dysfonctionnement, mais il s’agit d’un simple wrapper qui n’a pas évolué alors que ce qu’il encapsule, oui.

Conséquence pratique : si vous l’utilisez déjà et qu’elle fonctionne, il n’y a aucune urgence. Pour le nouveau code, utiliser directement un agent proxy ne représente qu’une ligne supplémentaire et supprime une couche obsolète de votre arborescence de dépendances — et comme l’extension est justement une couche d’encapsulation autour de cet agent, vous ne perdez rien d’autre que la syntaxe.

Vérifier que cela fonctionne réellement

Les quatre lignes les plus importantes.

const res = await request
  .get("https://api.ipify.org?format=json")
  .agent(agent);

console.log(res.body);

Exécutez-la avec et sans l’agent. Si l’adresse ne change pas, le proxy n’est pas dans le chemin d’accès. SuperAgent ne renvoie aucune erreur, aucun avertissement et aucune requête échouée lorsque cela se produit — le trafic passe simplement directement.

Trois raisons pour lesquelles une configuration ne produit généralement aucun effet :

Vous avez appelé .agent() sans argument, ce qui a créé une copie de SuperAgent persistante via un cookie au lieu de définir un agent HTTP. Il s’agit là d’un piège terminologique qui produit exactement ce symptôme.

Vous avez appliqué l’agent à la mauvaise requête. Le chaînage de SuperAgent s’effectue par requête ; ainsi, un agent défini lors d’un appel ne s’applique pas au suivant. Pour garantir un comportement cohérent, encapsulez la création de la requête dans une fonction.

Le type d’agent ne correspond pas à la cible. Un « HttpsProxyAgent » gère les cibles HTTPS ; une cible HTTP simple peut nécessiter la variante HTTP. proxy-agent contourne ce problème en choisissant pour vous.

Pour les proxys à ciblage géographique, la vérification de l’adresse ne suffit pas. Vérifiez à l’aide d’un test de résultat — demandez une information qui varie réellement d’une région à l’autre et assurez-vous que la réponse change en conséquence. Si un service de recherche indique le bon pays alors que votre API renvoie les données de votre région d’origine, cela signifie que le ciblage ne fonctionne pas là où il le devrait. Il s’agit là du modèle d’échec silencieux que nous avons décrit dans l’article « Pourquoi il est important de tester les proxys ».

Délais d'expiration via un proxy

Le modèle de délai d'expiration de SuperAgent est exceptionnellement performant, et mérite d'être utilisé à bon escient lorsqu'un proxy ajoute de la latence.

La documentation décrit deux paramètres. req.timeout({deadline: ms})

— ou req.timeout(ms)

— « définit un délai maximal pour que la requête entière (y compris tous les envois de données, les redirections et le temps de traitement du serveur) soit effectuée. Si la réponse n’est pas entièrement téléchargée dans ce délai, la requête sera interrompue. » Et req.timeout({response: ms})

« définit le délai maximal d’attente pour la réception du premier octet provenant du serveur, mais ne limite pas la durée totale du téléchargement. »

Les conseils de la documentation concernant le dimensionnement s’appliquent directement aux requêtes transitant par un proxy : « Le délai d’expiration de la réponse doit être au moins quelques secondes plus long que le temps nécessaire au serveur pour répondre, car il inclut également le temps nécessaire à la recherche DNS, aux connexions TCP/IP et TLS, ainsi qu’au temps de transmission des données de la requête. »

Via un proxy, chacune de ces phases prend plus de temps. Un point de sortie résidentiel ajoute une latence réelle par requête, qui est due à la distance plutôt qu’à une défaillance.

const res = await request
  .get("https://api.example.com/items")
  .agent(agent)
  .timeout({ response: 15000, deadline: 60000 });

La documentation recommande d’utiliser les deux, et la raison est la même que partout ailleurs : un délai d’expiration de réponse permet de détecter un serveur qui ne répond jamais, tandis qu’une date limite permet de détecter un serveur qui répond mais qui met ensuite beaucoup de temps à transmettre les données. Aucune des deux options, prise isolément, ne couvre les deux cas de figure.

Définissez les valeurs en fonction des mesures effectuées via le proxy plutôt que par habitude, sinon vous obtiendrez des échecs qui ressembleront à un proxy défaillant alors qu’il s’agit simplement d’une latence que vous n’avez pas prise en compte.

Gestion des erreurs

Le comportement par défaut de SuperAgent diffère de celui de la plupart des clients, ce qui revêt une importance particulière dans ce contexte.

La documentation est très claire : « Par défaut, SuperAgent considère les réponses 4xx et 5xx (ainsi que les réponses 3xx non gérées) comme des erreurs ». Elle ajoute que « ces informations d’état seront disponibles via err.status », et que ces erreurs « contiennent également un champ err.response ».

Ainsi, un échec d’authentification via un proxy se présente sous la forme d’un rejet plutôt que d’une réponse :

try {
  const res = await request.get(url).agent(agent).timeout({ deadline: 30000 });
  return res.body;
} catch (err) {
  if (err.status === 407) throw new Error("Proxy rejected credentials");
  if (err.status === 401) throw new Error("Target requires authentication");
  if (!err.status) throw new Error(`Network error: ${err.code} ${err.message}`);
  throw err;
}

La distinction entre les codes 407 et 401 mérite d’être prise en compte. Un code 407 signifie que le proxy vous a bloqué et que la cible n’a jamais été atteinte ; un code 401 signifie que le proxy a fonctionné et que la cible demande des identifiants. Il s’agit de situations différentes, nécessitant des solutions différentes, et elles peuvent facilement prêter à confusion lorsque les deux apparaissent sous forme d’erreurs renvoyées.

Une erreur sans err.status signifie qu’aucune réponse HTTP n’est arrivée, ce qui renvoie à la connexion plutôt qu’à l’authentification d’un utilisateur. ECONNREFUSED signifie que rien n’écoute à l’adresse du proxy ; ETIMEDOUT signifie que les paquets disparaissent.

Pour traiter certains codes d’erreur comme des succès — en interprétant un 404 comme des données plutôt que comme un échec —, utilisez .ok() :

.ok(res => res.status < 500)

Réessais : à utiliser avec prudence

SuperAgent intègre une fonctionnalité de réessai, assortie d’une restriction documentée qu’il convient de respecter.

const res = await request.get(url).agent(agent).retry(2);

La documentation explique que .retry() « réessaiera automatiquement les requêtes si elles échouent de manière temporaire ou si cet échec est dû à une connexion Internet instable », en acceptant un nombre de tentatives optionnel (1 par défaut) et une fonction de rappel appelée « avant chaque nouvelle tentative ». La fonction de rappel « peut renvoyer true/false pour déterminer si la requête doit être réessayée (mais le nombre maximal de tentatives est toujours appliqué) ».

Et la restriction, clairement énoncée dans la documentation : n’utilisez .retry() « qu’avec des requêtes idempotentes ».

Via un proxy, cela revêt une importance particulière, pour une raison bien précise. Un délai d’expiration n’est pas une preuve d’échec : la requête a peut-être atteint sa destination et abouti, tandis que la réponse a été perdue lors du trajet de retour. Il y a un saut supplémentaire où cela peut se produire. Réessayer une requête POST dans cette situation peut entraîner une duplication de l’écriture, et aucune configuration de réessai ne permet de garantir la sécurité de cette opération. Lorsque l’opération est critique, utilisez une clé d’idempotence si l’API en propose une.

La fonction de rappel est également l’endroit idéal pour éviter de réessayer en cas d’échec d’authentification, car un code d’erreur 407 dû à des identifiants incorrects produira un 407 à chaque tentative :

.retry(3, (err, res) => {
  if (res?.status === 407 || res?.status === 401) return false;
  return true;
})

Un wrapper qui vaut le coup d’être écrit

Le chaînage de SuperAgent s’effectue au cas par cas, ce qui signifie qu’il est facile d’oublier la configuration du proxy lors de l’appel qui compte vraiment. En encapsulant la création de la requête, on résout ce problème et on dispose d’un emplacement où placer le reste des paramètres par défaut.

import request from "superagent";
import { ProxyAgent } from "proxy-agent";

const agent = process.env.PROXY_URL ? new ProxyAgent(process.env.PROXY_URL) : undefined;

const UA = "AcmeBot/1.0 (+https://acme.example.com/bot)";

function req(method, url) {
  const r = request[method](url)
    .set("User-Agent", UA)
    .timeout({ response: 15000, deadline: 60000 })
    .retry(2, (err, res) => {
      if (res?.status === 407 || res?.status === 401) return false;
      if (res?.status === 429) return false;   // honour the rate limit instead
      return true;
    });
  return agent ? r.agent(agent) : r;
}

export const get = url => req("get", url);
export const post = url => req("post", url);

export async function verifyExit() {
  const res = await get("https://api.ipify.org?format=json");
  console.log(`Exit address: ${res.body.ip}`);
  return res.body.ip;
}

Cinq choix ont été délibérément faits ici.

Le proxy est facultatif et provient de l’environnement. En l’absence d’PROXY_URL, l’agent est undefined et les requêtes sont transmises directement, ce qui garantit un comportement prévisible en développement local et en production, sans avoir à créer de branches dans le code de votre application. Aucun identifiant n’apparaît dans le code source.

ProxyAgent sans argument de constructeur renverrait http_proxy et ses variantes si vous préfériez une configuration pilotée par l’environnement ; passer l’URL explicitement rend la source de vérité évidente, ce qui est généralement plus utile.

L’agent utilisateur est honnête et comporte une URL de contact. Cela ne coûte rien et change la donne lorsqu’un opérateur de site vous remarque.

Les nouvelles tentatives excluent les statuts pour lesquels il est inutile ou déplacé de réessayer. Un 407 dû à des identifiants incorrects restera un 407 à chaque fois ; un 429 est une instruction de ralentir, et réessayer dans ce cas transforme une limite temporaire en une limite plus longue.

Le fichier verifyExit() est exporté et appelé au démarrage. Six lignes qui transforment un proxy contourné en silence en une ligne dans les journaux — ce qui est le seul thème récurrent du fonctionnement des proxys dans chaque client, et la seule chose qu’aucune bibliothèque ne fera pour vous.

« .connect() » n’est pas un proxy

Cela mérite d’être signalé car cette fonctionnalité ressemble à un proxy, alors qu’elle n’en est pas un.

SuperAgent propose une méthode « .connect() » qui, selon la documentation, permet « d’ignorer la résolution DNS et de diriger toutes les requêtes vers une adresse IP spécifique ». Elle prend en charge un mappage, y compris une solution de secours « * » :

const res = await request.get("http://redir.example.com:555")
  .connect({
    "redir.example.com": "127.0.0.1",
    "www.example.com": false,
    "mapped.example.com": { host: "127.0.0.1", port: 8080 },
    "*": "proxy.example.com",
  });

La documentation précise que « les requêtes conserveront leur en-tête Host avec la valeur d’origine », et que la commande .connect(undefined) désactive cette fonctionnalité.

Il s’agit d’une redirection d’hôte, et non d’un proxy. Elle modifie l’adresse vers laquelle la connexion est établie tout en laissant la requête inchangée — il n’y a ni tunnel CONNECT, ni protocole proxy, ni authentification proxy. Cette fonctionnalité est destinée aux tests, et la documentation la classe à juste titre dans la section « Tests sur localhost ».

La ligne « "*": "proxy.example.com" » de l’exemple officiel est à l’origine de la confusion. Utilisez « .connect() » pour diriger les requêtes vers un serveur de test local ; utilisez un agent pour un véritable proxy.

Questions fréquentes

Comment utiliser un proxy avec SuperAgent ?

Transmettez un agent proxy à .agent() : créez un HttpsProxyAgent ou un SocksProxyAgent avec l’URL de votre proxy, puis transmettez-le à la requête. Vous pouvez également utiliser l’extension superagent-proxy, qui ajoute une méthode .proxy(uri) — bien que ce package n’ait pas fait l’objet d’une nouvelle version depuis 2021.

Quelle est la différence entre .agent() avec et sans argument ?

Sans argument, cette méthode crée une copie de SuperAgent conservant les cookies, avec son propre fichier jar et ses options par défaut. Avec un argument, elle définit la propriété http.Agent de Node pour cette requête, ce qui permet d’activer la prise en charge du proxy. Le nom commun à ces deux cas prête véritablement à confusion.

superagent-proxy est-il toujours maintenu ?

Il n’est pas obsolète, mais la version 3.0.0 date de septembre 2021, tandis que sa dépendance sous-jacente proxy-agent a continué à être mise à jour — la dernière fois en juin 2026. Pour le nouveau code, l’utilisation directe d’un agent proxy évite un wrapper obsolète, au prix d’une ligne de code supplémentaire.

Pourquoi mon proxy SuperAgent ne fonctionne-t-il pas ?

Le plus souvent, c’est parce que la fonction .agent() a été appelée sans argument, ce qui crée un « cookie jar » au lieu de configurer un proxy. Vérifiez également que l’agent a bien été appliqué à la bonne requête, car le chaînage SuperAgent s’effectue appel par appel. Vérifiez en interrogeant un service qui renvoie votre adresse — un proxy contourné ne génère aucune erreur.

SuperAgent respecte-t-il les variables d’environnement HTTP_PROXY ?

Pas de lui-même. Le paquet proxy-agent lit bien http_proxy, https_proxy et no_proxy ; ainsi, en créant un objet ProxyAgent() sans argument et en le transmettant à .agent(), vous obtenez un comportement déterminé par l’environnement.

Comment définir des délais d’expiration pour les requêtes transitant par le proxy ?

Utilisez les deux paramètres : .timeout({ response: 15000, deadline: 60000 }). Le délai d’expiration de la réponse limite l’attente du premier octet, tandis que le délai limite l’ensemble de la requête. Déterminez-les à partir de mesures effectuées via le proxy, car une sortie résidentielle ajoute une latence réelle aux phases DNS, de connexion et TLS.

Comment distinguer une erreur de proxy d’une erreur de cible ?

En fonction du code d’état. SuperAgent considère les codes 4xx et 5xx comme des erreurs ; interceptez et analysez donc err.status — un code 407 signifie que le proxy vous a rejeté et que la cible n’a jamais été atteinte, tandis qu’un code 401 signifie que le proxy a fonctionné et que la cible demande des identifiants. L’absence totale d’err.statuse signifie qu’aucune réponse HTTP n’est parvenue.

Puis-je utiliser .connect() comme proxy ?

Non. Cette méthode redirige les requêtes vers une adresse IP spécifique tout en conservant l’en-tête Host d’origine, ce qui correspond à un mappage d’hôte à des fins de test plutôt qu’à une mise en proxy. Il n’y a ni tunnel, ni protocole de proxy, ni authentification. Utilisez un agent pour bénéficier d’un véritable proxy.

Conclusion

SuperAgent ne dispose pas de son propre proxy ; vous avez donc le choix entre un agent et une extension — et l’agent est le meilleur choix par défaut, car ce sont les paquets maintenus qui effectuent le travail proprement dit dans les deux cas.

La terminologie constitue le principal piège. La commande .agent() sans argument vous fournit un « cookie jar » ; .agent(something) configure un agent HTTP. Les utilisateurs configurent la première commande, constatent que les requêtes aboutissent et en concluent que le proxy fonctionne. Personne ne les corrige, car un proxy contourné échoue silencieusement par définition.

C’est pourquoi il vaut la peine de prendre l’habitude de vérifier. Envoyez une requête à un service qui renvoie votre adresse, avec et sans l’agent, et vérifiez que la réponse change. Pour les travaux de ciblage géographique, allez plus loin et vérifiez que les contenus distincts selon les régions diffèrent réellement — l’adresse est la partie la plus facile et la moins informative.

Définissez ensuite les deux délais d’expiration, utilisez la condition « err.status » pour qu’un code 407 et un code 401 génèrent des messages différents, et évitez d’utiliser « .retry() » pour tout ce qui n’est pas idempotent. Via un proxy, il y a un saut supplémentaire où une requête réussie peut perdre sa réponse, et une nouvelle tentative dans cette situation constitue une duplication plutôt qu’une récupération.