Usar proxies en Deno y Bun: guía práctica con código y ProxyHat

Guía code-first para enrutar fetch() a través de proxies residenciales en Deno y Bun: Deno.createHttpClient, la opción proxy de Bun, sesiones sticky, SOCKS5 y patrones de producción.

Using Proxies in Deno and Bun: A Code-First Guide for Modern JavaScript

Si llegaste aquí buscando cómo usar proxies en Deno y Bun, la respuesta corta es que fetch() no lee configuración de proxy por defecto: en Deno necesitas Deno.createHttpClient({ proxy: { url, basicAuth } }) y pasarlo como { client }, mientras que en Bun basta con fetch(url, { proxy: 'http://user:pass@gate.proxyhat.com:8080' }). A continuación verás por qué esto ocurre, cómo codificar geo-segmentación y sesiones sticky, cuándo usar variables de entorno, y cómo montar un cliente robusto con reintentos, timeouts y certificados CA personalizados.

Por qué fetch() ignora los proxies en Deno y Bun

La API fetch() está estandarizada por WHATWG y no define ningún parámetro de proxy. El estándar asume que el transporte lo gestionan capas inferiores, por lo que cada runtime decide cómo exponerlo. Node.js lo delegó históricamente a librerías como undici y al agente HTTP, y Deno y Bun han seguido caminos distintos pero igualmente explícitos.

Deno implementa fetch() sobre su propio cliente HTTP basado en Rust, y exige crear un HttpClient configurado con proxy antes de la llamada. Bun, en cambio, expone una opción proxy directamente en el segundo argumento de fetch(), lo que reduce el boilerplate a una sola línea. Ambos enfoques son válidos; el de Deno ofrece más control (certificados CA, pools de conexión), el de Bun es más ergonómico para scripts rápidos.

Variables de entorno: el camino de HTTP_PROXY

Si solo necesitas un proxy global para todo el proceso, las variables HTTP_PROXY y HTTPS_PROXY son la opción más simple. Deno las respeta a partir de la versión 1.10+ cuando usas fetch() sin cliente personalizado, y Bun las lee automáticamente. Esto es útil para CLI y scripts de un solo uso, pero pierdes control granular: no puedes mezclar proxies residenciales y datacenter en el mismo proceso, ni rotar sesiones sticky por petición. En producción, casi siempre conviene la configuración por cliente.

Configuración básica en Deno con Deno.createHttpClient

El patrón idiomático en Deno es crear un cliente reutilizable y pasarlo a fetch() mediante la opción client. Esto te permite mantener un pool de conexiones y reutilizar TLS entre llamadas, lo que reduce la latencia unas decenas de milisegundos por petición en cargas medias.

// deno run --allow-net --allow-env proxy_deno.ts
const proxyUrl = 'http://user-country-US:PASSWORD@gate.proxyhat.com:8080';

const client = Deno.createHttpClient({
  proxy: {
    url: proxyUrl,
    // basicAuth es opcional si ya va en la URL, pero refuerza el header:
    basicAuth: { username: 'user-country-US', password: 'PASSWORD' },
  },
  // Para entornos con CA corporativa, añade tus certificados PEM:
  caCerts: [Deno.readTextFileSync('./corp-ca.pem')],
});

try {
  const res = await fetch('https://httpbin.org/ip', { client });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const json = await res.json();
  console.log('IP de salida:', json.origin);
} catch (err) {
  console.error('Petición fallida:', err);
} finally {
  client.close(); // libera el pool de conexiones
}

El método client.close() es importante en procesos largos: sin cerrarlo, las conexiones TLS se acumulan y pueden agotar el límite de file descriptors del sistema operativo, típicamente 1024 por proceso en Linux.

Configuración básica en Bun con la opción proxy

Bun expone la opción proxy directamente en fetch(), siguiendo el mismo formato de URL que HTTP_PROXY. No necesitas crear un cliente explícito, aunque Bun reutiliza conexiones internamente.

// bun run proxy_bun.ts
const proxy = 'http://user-country-DE-city-berlin:PASSWORD@gate.proxyhat.com:8080';

try {
  const res = await fetch('https://httpbin.org/ip', { proxy });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const json = await res.json();
  console.log('IP de salida:', json.origin);
} catch (err) {
  console.error('Petición fallida:', err);
}

Para SOCKS5, cambia el esquema y el puerto: socks5://user-country-DE:PASSWORD@gate.proxyhat.com:1080. Bun soporta SOCKS5 de forma nativa, mientras que en Deno necesitarás una librería de terceros o un túnel local.

Codificar geo-segmentación y sesiones sticky en el nombre de usuario

ProxyHat codifica las opciones de enrutamiento en el nombre de usuario, no en la URL de destino. Esto permite mantener la misma URL objetivo y cambiar el país, la ciudad o la sesión simplemente modificando el usuario. El formato es:

  • user-country-US — IP de salida en Estados Unidos.
  • user-country-DE-city-berlin — IP en Berlín, Alemania.
  • user-session-abc123 — sesión sticky: la misma IP durante toda la vida de la sesión.
  • user-country-US-session-abc123 — combina país y sesión sticky.

Las sesiones sticky son esenciales para flujos de login o carritos de compra donde el servidor de destino asocia la IP a una cookie o token. Si la IP cambia a mitad de sesión, el sitio puede invalidar la autenticación o disparar un CAPTCHA. En ProxyHat, una sesión sticky mantiene la misma IP durante aproximadamente 10 minutos de inactividad, suficiente para la mayoría de flujos de scraping.

// Generador de usuarios ProxyHat para Deno y Bun
function buildProxyUser(opts: {
  country?: string;
  city?: string;
  session?: string;
}): string {
  const parts = ['user'];
  if (opts.country) parts.push(`country-${opts.country}`);
  if (opts.city) parts.push(`city-${opts.city.toLowerCase()}`);
  if (opts.session) parts.push(`session-${opts.session}`);
  return parts.join('-');
}

// Ejemplo: 5 sesiones sticky en EE.UU. para scraping paralelo
const sessions = Array.from({ length: 5 }, (_, i) => `s${i}-${Date.now()}`);
const users = sessions.map((s) =>
  buildProxyUser({ country: 'US', session: s })
);
console.log(users[0]); // user-country-US-session-s0-1717986918

Rotar un pool de sesiones sticky con Promise.all y AbortController

Para objetivos con defensa anti-bot agresiva (SERPs, e-commerce, redes sociales), lo ideal no es una IP nueva por petición —eso genera patrones detectables— sino un pool de sesiones sticky que rota de forma controlada. Cada sesión mantiene una IP residencial estable, lo que imita el comportamiento de un usuario real. Lanza varias sesiones en paralelo con Promise.all y protege cada llamada con un AbortController para evitar que una petición colgada bloquee todo el lote.

// Bun o Deno: pool de sesiones sticky con timeout y reintentos
const PASSWORD = Deno?.env?.get('PROXYHAT_PASS') ?? Bun?.env?.PROXYHAT_PASS ?? '';
const TARGET = 'https://api.target.com/search?q=producto';
const SESSIONS = 8;
const TIMEOUT_MS = 15_000;
const MAX_RETRIES = 3;

function buildUser(session: string) {
  return `user-country-US-session-${session}`;
}

async function fetchWithProxy(session: string, attempt = 1): Promise<number> {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
  const proxy = `http://${buildUser(session)}:${PASSWORD}@gate.proxyhat.com:8080`;

  try {
    // En Deno, sustituye por fetch(TARGET, { client, signal })
    const res = await fetch(TARGET, { proxy, signal: ctrl.signal });
    if (res.status === 429 || res.status >= 500) {
      throw new Error(`HTTP ${res.status}`);
    }
    return res.status;
  } catch (err) {
    if (attempt < MAX_RETRIES) {
      const backoff = Math.min(1000 * 2 ** attempt, 8000);
      await new Promise((r) => setTimeout(r, backoff));
      return fetchWithProxy(session, attempt + 1);
    }
    throw err;
  } finally {
    clearTimeout(timer);
  }
}

const sessions = Array.from({ length: SESSIONS }, (_, i) => `pool-${i}-${Date.now()}`);
const results = await Promise.allSettled(
  sessions.map((s) => fetchWithProxy(s))
);

const ok = results.filter((r) => r.status === 'fulfilled').length;
console.log(`Peticiones OK: ${ok}/${SESSIONS}`);

Usar Promise.allSettled en lugar de Promise.all garantiza que una sesión fallida no cancele todo el lote. El backoff exponencial con techo de 8 segundos evita martillear el destino cuando hay errores 5xx o 429. Con 8 sesiones concurrentes y un timeout de 15 segundos, el lote completo debería completarse en menos de 20 segundos en condiciones normales.

Reintentos, circuit breakers y reutilización de conexiones

En producción necesitas más que reintentos simples. Un patrón robusto combina tres elementos: backoff exponencial con jitter, un circuit breaker que pare de enviar tráfico si la tasa de error supera un umbral, y reutilización del cliente HTTP para mantener vivas las conexiones TLS. En Deno, el HttpClient reutiliza conexiones automáticamente; en Bun, el pool interno hace lo mismo, pero solo si no recreas el proceso.

// Circuit breaker simple para scraping con proxies en Deno
interface BreakerState {
  failures: number;
  openUntil: number;
}

const breaker: BreakerState = { failures: 0, openUntil: 0 };
const THRESHOLD = 5;
const COOLDOWN_MS = 30_000;

function isOpen(): boolean {
  return Date.now() < breaker.openUntil;
}

function recordFailure() {
  breaker.failures++;
  if (breaker.failures >= THRESHOLD) {
    breaker.openUntil = Date.now() + COOLDOWN_MS;
    breaker.failures = 0;
    console.warn('Circuit breaker abierto por 30s');
  }
}

function recordSuccess() {
  breaker.failures = 0;
}

async function safeFetch(url: string, client: Deno.HttpClient) {
  if (isOpen()) throw new Error('Circuit breaker abierto');
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), 12_000);
  try {
    const res = await fetch(url, { client, signal: ctrl.signal });
    if (!res.ok) recordFailure();
    else recordSuccess();
    return res;
  } catch {
    recordFailure();
    throw new Error('fetch abortado o fallido');
  } finally {
    clearTimeout(t);
  }
}

Comparación: cliente nativo vs ProxyHat SDK

El SDK de ProxyHat para Node funciona también bajo Deno y Bun gracias a la compatibilidad con el ecosistema npm. La principal ventaja del SDK es que gestiona rotación, reintentos y telemetría por ti, pero para casos donde necesitas control total del TLS o del pool, el cliente nativo es más flexible.

Aspectofetch + proxy nativoProxyHat SDK
BoilerplateMás código manualMínimo, una llamada
Rotación automáticaManual (gestionas sesiones)Integrada
Control de TLS / CACompleto en Deno (caCerts)Limitado al SDK
CompatibilidadDeno, Bun, NodeDeno, Bun, Node
TelemetríaManual (logs propios)Integrada
// ProxyHat SDK bajo Bun o Deno (npm: @proxyhat/node)
import { ProxyHat } from '@proxyhat/node';

const ph = new ProxyHat({
  username: 'user',
  password: PASSWORD,
  // rotate: 'per-request' o 'sticky'
  rotate: 'sticky',
  country: 'US',
});

const result = await ph.fetch('https://api.target.com/data');
console.log('Status:', result.status);
console.log('IP usada:', result.proxyIp);

Por qué proxies residenciales para objetivos con bloqueo alto

Los proxies datacenter son más rápidos y baratos, pero sus rangos de IP están publicados en bases de datos como las de ASN de IANA y son trivialmente detectables. Los proxies residenciales usan IPs asignadas a ISPs reales, por lo que el destino no puede distinguirlos de un usuario doméstico. Para SERP scraping, monitorización de precios en e-commerce y verificación de anuncios, la diferencia entre un 10% y un 95% de éxito suele ser residencial vs datacenter.

Combina residenciales con sesiones sticky para maximizar la fiabilidad: una IP estable reduce la probabilidad de CAPTCHA, y si aparece uno, puedes pausar esa sesión y reanudarla más tarde sin perder el contexto. Consulta las ubicaciones disponibles y la página de precios para dimensionar tu pool según el volumen.

Consideraciones éticas y legales

Scrapear datos públicos no es ilegal per se, pero hay matices importantes. En EE.UU., el CFAA ha sido interpretado de forma restrictiva tras el caso hiQ Labs v. LinkedIn, pero saltarse medidas de acceso sigue siendo arriesgado. En la UE, el GDPR aplica a datos personales: si tu scraping recopila datos de usuarios identificables, necesitas base legal. Reglas prácticas:

  • Usa la API oficial si existe: es más estable y legalmente más seguro.
  • Respeta robots.txt y los términos de servicio del sitio.
  • No recopiles datos personales sin base legal (consentimiento o interés legítimo).
  • Limita la concurrencia para no degradar el servicio del destino.

Para casos de uso concretos, revisa nuestras guías de web scraping y SERP tracking.

Errores comunes y cómo evitarlos

  • Olvidar client.close() en Deno: en procesos largos, agotas file descriptors. Cierra el cliente al final o reutilízalo.
  • Mezclar HTTP y HTTPS sin HTTPS_PROXY: si usas variables de entorno, define ambas; si no, las peticiones HTTPS no se enrutarán.
  • Timeouts demasiado largos: 60 segundos sin AbortController bloquea el lote. Usa 10–15 segundos por petición.
  • Reintentos sin backoff: martillar un 429 sin esperar empeora el bloqueo. Usa backoff exponencial con jitter.
  • Ignorar el certificado CA corporativo: en redes con TLS inspection, necesitas caCerts en Deno o NODE_EXTRA_CA_CERTS en Bun.

Puntos clave

Usar proxies en Deno y Bun requiere configuración explícita: Deno.createHttpClient en Deno y la opción proxy en Bun. Codifica geo-segmentación y sesiones en el nombre de usuario, rota un pool de sesiones sticky con Promise.allSettled, protege con AbortController y aplica backoff exponencial. Los proxies residenciales son la opción correcta para objetivos con bloqueo alto; los datacenter quedan para casos de baja fricción.

FAQ

¿Qué es usar proxies en Deno y Bun? Es configurar el cliente HTTP de estos runtimes para enrutar fetch() a través de un servidor proxy, ya sea con Deno.createHttpClient({ proxy }) en Deno o con la opción proxy directa en Bun.

¿Por qué importa para usuarios de proxies? Porque fetch() no soporta proxies por defecto: sin configuración explícita, tu IP real queda expuesta y el scraping falla en sitios con geo-restricciones o anti-bot.

¿Qué tipo de proxy funciona mejor? Los proxies residenciales son los más fiables para objetivos con bloqueo alto, porque usan IPs de ISPs reales. Los datacenter son útiles para tareas de baja fricción donde la velocidad importa más que la evasión.

¿Cómo evitas bloqueos al implementar proxies en Deno y Bun? Combina sesiones sticky, rotación de un pool de IPs, timeouts con AbortController, backoff exponencial y respeto a robots.txt y límites de concurrencia.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog