Si vous développez des outils d'automatisation avec Deno ou Bun, vous avez probablement découvert que fetch() ignore les proxies par défaut. Contrairement à Node.js où des bibliothèques comme axios ou got gèrent les proxies depuis des années, les runtimes JavaScript modernes nécessitent une configuration explicite. Ce guide explique comment utiliser des proxies dans Deno et Bun, avec des exemples exécutables couvrant la rotation d'IP, les sessions sticky, le géo-ciblage SOCKS5, et les patterns de production comme les retries avec backoff.
Pourquoi utiliser des proxies dans Deno et Bun : le problème de fetch()
Le standard fetch() défini par le WHATWG ne spécifie aucun mécanisme natif pour les proxies HTTP. La spécification décrit la récupération de ressources réseau mais délègue la gestion des proxies aux implémentations sous-jacentes. En Node.js, des bibliothèques tierques comblent cette lacune depuis longtemps. Deno et Bun, en revanche, ont chacun adopté une approche différente.
Deno expose Deno.createHttpClient() qui crée un client HTTP personnalisé avec un proxy configuré, passé ensuite à fetch() via l'option client. Bun, plus direct, accepte une option proxy directement dans l'appel fetch(). Cette différence architecturale a un impact direct sur votre code : vous ne pouvez pas simplement définir HTTP_PROXY et espérer que tout fonctionne. Selon la documentation MDN, fetch() est conçu comme une API web standard, sans considération pour les proxies — c'est aux runtimes de combler ce vide.
Configurer un proxy avec Deno.createHttpClient
Deno propose la méthode Deno.createHttpClient() qui retourne un objet HttpClient. Vous passez cet objet à fetch() via l'option client. Voici un exemple basique avec ProxyHat :
// Deno : proxy HTTP basique avec ProxyHat
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: "user-country-US",
password: "votre_mot_de_passe",
},
},
});
const response = await fetch("https://httpbin.org/ip", { client });
const data = await response.json();
console.log("IP de sortie :", data.origin);
client.close();
Notez l'appel client.close() — Deno recommande de fermer explicitement le client pour libérer les ressources. Oublier cette étape peut provoquer des fuites de connexions dans les scripts long-running. Pour un guide complet, consultez la documentation officielle de Deno.
Gestion des erreurs et timeout avec AbortController
// Deno : proxy avec timeout et gestion d'erreurs
async function fetchWithProxy(url: string): Promise<Response> {
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: "user-country-US-session-abc123",
password: "votre_mot_de_passe",
},
},
});
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, { client, signal: controller.signal });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") {
throw new Error("Timeout : la requete a depasse 10 secondes");
}
throw error;
} finally {
clearTimeout(timeout);
client.close();
}
}
Ce pattern est essentiel en production : un proxy résidentiel peut introduire 200 à 800 ms de latence supplémentaire, et sans timeout, une requête bloquée peut attendre indéfiniment.
Configurer un proxy avec Bun fetch
Bun adopte une approche plus concise : l'option proxy est acceptée directement par fetch(). Consultez la documentation de Bun pour les détails.
// Bun : proxy HTTP en une ligne avec ProxyHat
const response = await fetch("https://httpbin.org/ip", {
proxy: "http://user-country-US:votre_mot_de_passe@gate.proxyhat.com:8080",
});
const data = await response.json();
console.log("IP de sortie :", data.origin);
Cette syntaxe est plus directe mais présente un inconvénient : les identifiants sont visibles en clair dans l'URL. Pour les projets en production, stockez les credentials dans des variables d'environnement.
Timeout et AbortController avec Bun
// Bun : proxy avec timeout et variables d'environnement
async function fetchWithProxy(url: string): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const proxyUser = process.env.PROXYHAT_USER;
const proxyPass = process.env.PROXYHAT_PASS;
const response = await fetch(url, {
proxy: `http://${proxyUser}-country-US:${proxyPass}@gate.proxyhat.com:8080`,
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response;
} finally {
clearTimeout(timeout);
}
}
SOCKS5, géo-ciblage et sessions sticky
ProxyHat supporte le protocole SOCKS5 sur le port 1080. Le géo-ciblage (pays, ville) et les sessions sticky sont encodés directement dans le nom d'utilisateur, ce qui évite d'avoir à gérer des en-têtes supplémentaires. Consultez la liste complète des localisations disponibles.
// Deno : SOCKS5 avec geo-ciblage et session sticky
const client = Deno.createHttpClient({
proxy: {
url: "socks5://gate.proxyhat.com:1080",
basicAuth: {
username: "user-country-DE-city-berlin-session-abc123",
password: "votre_mot_de_passe",
},
},
});
const response = await fetch("https://httpbin.org/ip", { client });
console.log(await response.json());
client.close();
// Bun : SOCKS5 avec geo-ciblage et session sticky
const response = await fetch("https://httpbin.org/ip", {
proxy: "socks5://user-country-DE-city-berlin-session-abc123:votre_mot_de_passe@gate.proxyhat.com:1080",
});
console.log(await response.json());
Le format du nom d'utilisateur suit ce schéma : user-country-{PAYS}-city-{VILLE}-session-{ID}. Chaque partie est optionnelle mais doit respecter cet ordre. La session sticky garantit que toutes les requêtes avec le même ID de session sortent par la même IP, ce qui est crucial pour maintenir un état de connexion sur des sites qui exigent une cohérence d'IP.
Variables d'environnement vs configuration par client
Deno et Bun peuvent tous deux lire les variables HTTP_PROXY et HTTPS_PROXY, mais leur traitement diffère. Bun lit automatiquement HTTP_PROXY pour les requêtes fetch() depuis la version 1.1. Deno, en revanche, nécessite une configuration explicite via createHttpClient à moins que vous n'utilisiez l'option --allow-env avec un client qui hérite de l'environnement.
| Critère | Deno | Bun |
|---|---|---|
| Proxy via env var | Manuel via createHttpClient | Automatique depuis v1.1 |
| Proxy par requête | Option client | Option proxy |
| Support SOCKS5 | Oui (port 1080) | Oui (port 1080) |
| Authentification basique | basicAuth dans config | Dans l'URL du proxy |
| Fermeture explicite | client.close() requis | Non requis |
| CA personnalisé | Option caCerts | Non documenté |
La configuration par client est préférable quand :
- Vous avez besoin de différents proxies pour différentes requêtes (rotation d'IP)
- Vous gérez des sessions sticky avec des identifiants de session distincts
- Vous voulez éviter qu'un proxy global affecte d'autres parties du code
- Vous testez différents pays ou villes en parallèle
Rotation de sessions résidentielles en concurrent
Pour les cibles avec une protection anti-bot élevée — suivi SERP, sites e-commerce, plateformes de billets — les proxies résidentiels sont essentiels. Un proxy datacenter peut être bloqué en moins de 100 requêtes, tandis qu'un résidentiel maintient un taux de succès supérieur à 95% sur des milliers de requêtes. La rotation de sessions sticky permet de distribuer la charge tout en conservant la cohérence d'IP par session.
// Bun : rotation de sessions sticky en concurrent avec Promise.all
const PROXYHAT_GATE = "gate.proxyhat.com:8080";
const PROXYHAT_USER = process.env.PROXYHAT_USER!;
const PROXYHAT_PASS = process.env.PROXYHAT_PASS!;
interface ScrapResult {
url: string;
status: number;
body: string;
error?: string;
}
async function fetchWithStickySession(
url: string,
sessionId: string,
country: string,
timeoutMs: number
): Promise<ScrapResult> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
const proxyUrl = `http://${PROXYHAT_USER}-country-${country}-session-${sessionId}:${PROXYHAT_PASS}@${PROXYHAT_GATE}`;
try {
const response = await fetch(url, {
proxy: proxyUrl,
signal: controller.signal,
});
const body = await response.text();
return { url, status: response.status, body };
} catch (error) {
return { url, status: 0, body: "", error: String(error) };
} finally {
clearTimeout(timeout);
}
}
// Lancer 20 requetes concurrentes avec des sessions differentes
const urls = Array.from(
{ length: 20 },
(_, i) => `https://httpbin.org/anything?id=${i}`
);
const sessions = urls.map((_, i) => `sess-${Date.now()}-${i}`);
const results = await Promise.all(
urls.map((url, i) => fetchWithStickySession(url, sessions[i], "US", 15000))
);
const successCount = results.filter((r) => r.status === 200).length;
console.log(`Taux de succes : ${successCount}/${results.length}`);
// Pour Deno : remplacer l'option `proxy` par :
// const client = Deno.createHttpClient({ proxy: { url: proxyUrl } });
// const response = await fetch(url, { client, signal: controller.signal });
// client.close();
Avec 100 sessions concurrentes, vous pouvez théoriquement traiter 100 requêtes en parallèle, chacune avec une IP résidentielle distincte. La clé est d'utiliser un identifiant de session unique par requête pour forcer la rotation d'IP, tout en gardant le même identifiant si vous devez enchaîner plusieurs requêtes sur le même site.
Production : retries, backoff exponentiel et CA personnalisés
Retry avec backoff exponentiel
En production, les erreurs réseau, les timeouts proxy et les réponses 429 (rate limiting) sont inévitables. Un retry avec backoff exponentiel et jitter est le standard de l'industrie pour gérer ces échecs transitoires.
// Bun/Deno : retry avec backoff exponentiel et jitter
async function fetchWithRetry(
url: string,
proxyUrl: string,
maxRetries: number = 3,
baseDelayMs: number = 1000
): Promise<Response> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
const response = await fetch(url, {
proxy: proxyUrl,
signal: controller.signal,
});
clearTimeout(timeout);
if (response.status === 429 || response.status >= 500) {
throw new Error(`HTTP ${response.status} -- reessayable`);
}
return response;
} catch (error) {
lastError = error as Error;
const jitter = Math.random() * 500;
const delay = baseDelayMs * Math.pow(2, attempt) + jitter;
console.log(
`Tentative ${attempt + 1} echouee, retry dans ${Math.round(delay)}ms`
);
await new Promise((r) => setTimeout(r, delay));
}
}
throw lastError;
}
// Utilisation
const proxyUrl = `http://${process.env.PROXYHAT_USER}-country-FR:${process.env.PROXYHAT_PASS}@gate.proxyhat.com:8080`;
const response = await fetchWithRetry(
"https://api.exemple.com/data",
proxyUrl
);
CA personnalisé dans Deno
Si votre infrastructure utilise un certificat TLS personnalisé (proxy d'entreprise, inspection SSL), Deno permet de spécifier des CAs via l'option caCerts. Bun ne propose pas d'équivalent documenté à ce jour.
// Deno : proxy avec CA personnalise
const caCert = await Deno.readTextFile("./custom-ca.pem");
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: "user-country-FR",
password: "votre_mot_de_passe",
},
},
caCerts: [caCert],
});
const response = await fetch("https://httpbin.org/ip", { client });
console.log(await response.json());
client.close();
Wrapper manuel équivalent au SDK ProxyHat
Le SDK ProxyHat fonctionne sous Deno et Bun via la compatibilité npm. En attendant, voici un wrapper manuel qui construit les URLs de proxy avec les mêmes conventions :
// Wrapper equivalent au SDK ProxyHat -- fonctionne sous Deno et Bun
function buildProxyUrl(opts: {
user: string;
pass: string;
country?: string;
city?: string;
session?: string;
socks5?: boolean;
}): string {
const proto = opts.socks5 ? "socks5" : "http";
const port = opts.socks5 ? "1080" : "8080";
let username = opts.user;
if (opts.country) username += `-country-${opts.country}`;
if (opts.city) username += `-city-${opts.city}`;
if (opts.session) username += `-session-${opts.session}`;
return `${proto}://${username}:${opts.pass}@gate.proxyhat.com:${port}`;
}
// Exemple : rotation avec pays differents
const proxyUS = buildProxyUrl({
user: "votre_utilisateur",
pass: "votre_mot_de_passe",
country: "US",
session: "task-001",
});
// => http://votre_utilisateur-country-US-session-task-001:votre_mot_de_passe@gate.proxyhat.com:8080
const proxyDEsocks = buildProxyUrl({
user: "votre_utilisateur",
pass: "votre_mot_de_passe",
country: "DE",
city: "berlin",
session: "task-002",
socks5: true,
});
// => socks5://votre_utilisateur-country-DE-city-berlin-session-task-002:votre_mot_de_passe@gate.proxyhat.com:1080
Erreurs courantes et cas limites
- Oublier
client.close()dans Deno : provoque des fuites de connexions. Utilisez toujourstry/finallypour garantir la fermeture. - Mélanger HTTP et HTTPS dans l'URL du proxy : l'URL du proxy doit toujours utiliser
http://(ousocks5://), même si la requête cible est en HTTPS. Le tunnel TLS est établi viaCONNECT. - Sessions sticky trop longues : une session sticky conserve la même IP, mais cette IP peut changer si le nœud résidentiel se déconnecte. Pour les sessions longues, prévoyez un retry qui génère un nouvel ID de session.
- Concurrency excessive : lancer 500 requêtes simultanées avec 500 sessions peut saturer la bande passante. Utilisez un semaphore ou un pool limité (par exemple, 50 requêtes concurrentes maximum).
- Ignorer les en-têtes
retry-after: si la cible renvoie un 429 avecRetry-After: 30, respectez ce délai avant de retry.
Scraping éthique et cadre légal
L'utilisation de proxies pour le web scraping soulève des questions légales importantes :
- Aux États-Unis : le Computer Fraud and Abuse Act (CFAA) peut s'appliquer. La jurisprudence hiQ Labs v. LinkedIn a partiellement clarifié que le scraping de données publiquement accessibles ne viole pas nécessairement le CFAA, mais prudence. Consultez les ressources de l'EFF sur le CFAA.
- Dans l'UE : le RGPD s'applique aux données personnelles. Si vous scrapez des données personnelles, vous devez avoir une base légale (consentement, intérêt légitime, etc.).
- Bonnes pratiques :
- Privilégiez les API officielles quand elles existent — c'est plus fiable et légalement plus sûr
- Respectez les fichiers
robots.txt - Limitez le taux de requêtes (1 à 2 req/s par IP est une bonne pratique)
- Ne scrapez que des données publiquement accessibles sans authentification
- Consultez les conditions d'utilisation des sites cibles
Pour des besoins de production, consultez nos tarifs ProxyHat et choisissez le plan adapté à votre volume de requêtes.
Points clés à retenir
Key Takeaways
fetch()dans Deno et Bun ignore les proxies par défaut — configurez-les explicitement- Deno utilise
Deno.createHttpClient({ proxy })+ optionclientdansfetch()- Bun accepte
proxydirectement dansfetch()— une seule ligne suffit- Le géo-ciblage et les sessions sticky s'encodent dans le nom d'utilisateur :
user-country-US-session-abc123- SOCKS5 disponible sur le port
1080, HTTP sur le port8080- Pour la production : retries avec backoff exponentiel, timeout via
AbortController, rotation de sessions sticky- Les proxies résidentiels sont indispensables pour les cibles avec protection anti-bot (taux de succès > 95%)
- Toujours fermer les clients Deno avec
client.close()pour éviter les fuites
Prêt à démarrer ? Configurez votre premier proxy ProxyHat avec les exemples ci-dessus et explorez nos localisations pour géo-cibler vos requêtes. Pour des cas d'usage avancés comme le suivi SERP ou le web scraping, les proxies résidentiels avec rotation de sessions sont le choix recommandé.






