← Torna al blog

Undici ProxyAgent in Node.js: configurazione di proxy HTTP/SOCKS5 e rotazione IP senza dipendenze aggiuntive

Analizziamo come utilizzare il ProxyAgent integrato di undici in Node.js per lavorare con proxy HTTP e SOCKS5, organizzare la rotazione degli IP e fare a meno di https-proxy-agent e socks-proxy-agent.

📅18 ottobre 2026

La maggior parte delle guide sui proxy in Node.js consiglia di includere nel progetto https-proxy-agent, socks-proxy-agent e un paio di altri pacchetti. In realtà, a partire dalla versione 18 di Node.js, il client HTTP integrato undici include già ProxyAgent, che copre la maggior parte delle esigenze di parsing e testing API tramite proxy. In questo articolo vedremo come configurare il tunneling HTTP/HTTPS tramite undici, organizzare la rotazione IP tra le richieste e cosa fare se è necessario SOCKS5, senza voler aggiungere dipendenze extra.

Perché undici invece di axios e request

undici è un client HTTP/1.1 e HTTP/2, sviluppato dal team di Node.js e, a partire dalla versione 18, è alla base del fetch globale. Non ha dipendenze transitive, è più veloce di axios nella maggior parte dei benchmark sul connection pooling e, cosa importante per il nostro argomento, ha un modulo nativo per lavorare con i proxy tramite tunneling HTTP CONNECT.

Quando si esegue il parsing di Wildberries, Ozon o si testano creatività pubblicitarie da diverse aree geografiche, ogni pacchetto in più in node_modules rappresenta un potenziale punto di guasto durante l'aggiornamento di Node.js. Undici viene fornito con il runtime, quindi si ottiene un'implementazione aggiornata di TLS, HTTP/2 e keep-alive senza il rischio che una libreria abbandonata si rompa in una nuova versione principale.

L'installazione è estremamente semplice:

npm install undici

Se si utilizza Node.js 20+, parte dell'API è disponibile globalmente senza importazione, ma per ProxyAgent è comunque necessario importarlo, poiché non è esportato nel fetch globale per impostazione predefinita.

Configurazione di base di ProxyAgent

Un esempio minimo funzionante di richiesta tramite proxy è il seguente:

import { ProxyAgent, fetch } from 'undici';

const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');

const response = await fetch('https://httpbin.org/ip', {
  dispatcher: proxyAgent,
});

const data = await response.json();
console.log(data);

Il punto chiave è il parametro dispatcher. Non è un'opzione agent, come nel vecchio modulo http, ma è il dispatcher di undici, che intercetta la connessione prima della stretta di mano TLS e stabilisce un tunnel tramite il metodo CONNECT all'host di destinazione. Per le richieste HTTPS tramite proxy, questo è critico: un normale proxy HTTP non vede il contenuto del traffico crittografato, ma semplicemente inoltra i byte all'interno del tunnel TCP.

Se si lavora non con fetch, ma con il basso livello undici.request, la sintassi è simile:

import { request, ProxyAgent } from 'undici';

const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');

const { statusCode, body } = await request('https://api.example.com/data', {
  dispatcher: proxyAgent,
});

console.log(statusCode);
for await (const chunk of body) {
  console.log(chunk.toString());
}

Per i parser di produzione, che effettuano centinaia di richieste al minuto, è importante non creare un nuovo ProxyAgent per ogni richiesta — questo distrugge il connection pooling. Creare un'istanza dell'agente una sola volta per il pool di connessioni e riutilizzarla tra le chiamate.

Autenticazione del proxy con nome utente e password

La maggior parte dei fornitori di proxy a pagamento fornisce accesso tramite coppie di nome utente/password, piuttosto che tramite IP-whitelisting. In undici, le credenziali vengono passate direttamente nell'URL del proxy:

import { ProxyAgent, fetch } from 'undici';

const login = 'user123';
const password = 'pass456';
const proxyUrl = `http://${login}:${password}@gate.proxycove.com:9000`;

const proxyAgent = new ProxyAgent(proxyUrl);

const response = await fetch('https://httpbin.org/ip', {
  dispatcher: proxyAgent,
});

console.log(await response.json());

Nota: se il nome utente o la password contengono caratteri speciali (@, :, /), devono essere codificati tramite encodeURIComponent, altrimenti il parser URL all'interno di undici non analizzerà correttamente la stringa di connessione e si riceverà un errore di autenticazione 407 invece di un messaggio comprensibile.

Per compiti in cui è importante la stabilità della sessione — ad esempio, l'autenticazione su un marketplace legata a un solo IP durante la sessione — è più conveniente utilizzare proxy residenziali con sessioni sticky: l'IP non cambia per un periodo di tempo specificato, il che significa che il servizio non vede "salti" tra le aree geografiche e non richiede un nuovo captcha ad ogni richiesta.

Rotazione del proxy tra le richieste

Se si dispone di un elenco di N proxy e l'obiettivo è distribuire le richieste tra di essi, la soluzione più semplice è un round-robin senza librerie di terze parti per le code:

import { ProxyAgent, fetch } from 'undici';

const proxyList = [
  'http://user:[email protected]:8000',
  'http://user:[email protected]:8000',
  'http://user:[email protected]:8000',
];

let currentIndex = 0;

function getNextAgent() {
  const proxyUrl = proxyList[currentIndex];
  currentIndex = (currentIndex + 1) % proxyList.length;
  return new ProxyAgent(proxyUrl);
}

async function fetchWithRotation(url) {
  const agent = getNextAgent();
  return fetch(url, { dispatcher: agent });
}

for (let i = 0; i < 10; i++) {
  const res = await fetchWithRotation('https://httpbin.org/ip');
  console.log(await res.json());
}

Questo approccio funziona bene per il parsing delle schede prodotto o il monitoraggio dei prezzi, quando ogni richiesta successiva è logicamente indipendente dalla precedente. Per compiti in cui è necessaria un'elevata velocità di cambio IP per ogni richiesta senza gestione manuale dell'elenco, è più semplice utilizzare proxy con rotazione automatica da parte del fornitore — in tal caso, tutto il codice sopra si riduce a un unico URL statico del gateway, mentre la rotazione è gestita da proxy di data center o pool residenziali con rotazione a timer.

Se è necessaria non solo una rotazione circolare, ma ponderata — ad esempio, dare priorità ai proxy più veloci — aggiungere un peso nella struttura dei dati:

const weightedProxies = [
  { url: 'http://user:[email protected]:8000', weight: 5 },
  { url: 'http://user:[email protected]:8000', weight: 1 },
];

function pickWeighted(list) {
  const total = list.reduce((sum, p) => sum + p.weight, 0);
  let rnd = Math.random() * total;
  for (const p of list) {
    if (rnd < p.weight) return p.url;
    rnd -= p.weight;
  }
  return list[0].url;
}

Controllo della vitalità del proxy ed esclusione degli IP "morti"

Un pool di proxy nel tempo "scade": alcuni IP vengono bannati dal sito di destinazione, altri smettono di rispondere. Per evitare che la rotazione invii richieste attraverso indirizzi morti, aggiungere un health-check con timeout tramite il AbortController integrato:

import { ProxyAgent, fetch } from 'undici';

async function isProxyAlive(proxyUrl, timeoutMs = 5000) {
  const agent = new ProxyAgent(proxyUrl);
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const res = await fetch('https://httpbin.org/ip', {
      dispatcher: agent,
      signal: controller.signal,
    });
    return res.status === 200;
  } catch {
    return false;
  } finally {
    clearTimeout(timer);
  }
}

async function filterAliveProxies(proxyList) {
  const checks = await Promise.all(
    proxyList.map(async (url) => ({ url, alive: await isProxyAlive(url) }))
  );
  return checks.filter((c) => c.alive).map((c) => c.url);
}

Esegui questo controllo ogni pochi minuti con un timer in background e aggiorna il pool attivo, non l'intero elenco statico. Questo è particolarmente importante se si mantengono proxy propri su IP di data center economici — lì la percentuale di guasti è più alta rispetto ai pool gestiti dai fornitori.

SOCKS5 senza librerie di terze parti

Una nota importante: il ProxyAgent integrato in undici funziona tramite tunneling HTTP CONNECT e "out of the box" comprende solo proxy HTTP/HTTPS. Il protocollo SOCKS5 è strutturato diversamente — è una stretta di mano binaria a livello TCP, e undici non lo analizza nativamente. Se hai assolutamente bisogno di SOCKS5 senza installare socks-proxy-agent, puoi implementare un connettore minimo manualmente tramite il modulo net e l'opzione connect in un Agent personalizzato:

import net from 'node:net';
import { Agent, fetch } from 'undici';

function socksHandshake(socket, targetHost, targetPort) {
  return new Promise((resolve, reject) => {
    // Passo 1: saluto, metodo senza autenticazione
    socket.write(Buffer.from([0x05, 0x01, 0x00]));

    socket.once('data', (greeting) => {
      if (greeting[1] !== 0x00) {
        return reject(new Error('SOCKS5: metodo di autenticazione non supportato'));
      }

      const hostBuf = Buffer.from(targetHost, 'utf8');
      const request = Buffer.concat([
        Buffer.from([0x05, 0x01, 0x00, 0x03, hostBuf.length]),
        hostBuf,
        Buffer.from([targetPort >> 8, targetPort & 0xff]),
      ]);

      socket.write(request);

      socket.once('data', (reply) => {
        if (reply[1] !== 0x00) {
          return reject(new Error('SOCKS5: connessione rifiutata'));
        }
        resolve(socket);
      });
    });
  });
}

const socksAgent = new Agent({
  connect: (options, callback) => {
    const socket = net.connect(1080, 'socks-proxy.example.com', async () => {
      try {
        await socksHandshake(socket, options.hostname, 443);
        callback(null, socket);
      } catch (err) {
        callback(err, null);
      }
    });
    socket.on('error', (err) => callback(err, null));
  },
});

const response = await fetch('https://httpbin.org/ip', { dispatcher: socksAgent });
console.log(await response.json());

Questa è una stretta di mano semplificata senza autenticazione tramite nome utente e password (metodo 0x02) e senza supporto per IPv6 — per la produzione è consigliabile aggiungere un ramo con autenticazione e gestione di diversi tipi di indirizzi. Ma per comprendere la meccanica, questo è sufficiente: SOCKS5 non richiede TLS all'interno del tunnel durante la fase di handshake, a differenza di HTTP CONNECT, quindi il codice è più breve di quanto sembri a prima vista.

Nella pratica, SOCKS5 viene utilizzato più spesso non per il parsing delle pagine web, ma per il forwarding del traffico delle applicazioni mobili e dei browser anti-detect come Dolphin Anty o AdsPower, dove il protocollo è necessario per lavorare a livello di socket, non di richieste HTTP. Se il tuo compito è proprio il traffico HTTP/HTTPS in Node.js, di solito il ProxyAgent integrato è sufficiente e non ha senso armeggiare con un connettore SOCKS5 manuale.

Gestione dei timeout e logica di retry

I proxy interrompono le connessioni più frequentemente rispetto a una connessione diretta — questo è normale per le reti residenziali, dove un nodo può andare offline in qualsiasi momento. È necessaria una logica di retry con ritardo esponenziale:

import { ProxyAgent, fetch } from 'undici';

async function fetchWithRetry(url, proxyList, maxRetries = 3) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const proxyUrl = proxyList[attempt % proxyList.length];
    const agent = new ProxyAgent(proxyUrl);
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 8000);

    try {
      const res = await fetch(url, { dispatcher: agent, signal: controller.signal });
      clearTimeout(timer);
      if (res.status >= 500) throw new Error(`Errore del server: ${res.status}`);
      return res;
    } catch (err) {
      clearTimeout(timer);
      lastError = err;
      const delay = 500 * 2 ** attempt;
      await new Promise((r) => setTimeout(r, delay));
    }
  }

  throw new Error(`Tutti i tentativi esauriti: ${lastError?.message}`);
}

Questo wrapper risolve anche il problema della rotazione in caso di errori: se il proxy attuale fallisce, il tentativo successivo va automaticamente su un altro IP dall'elenco, invece di continuare a colpire lo stesso nodo morto.

Confronto tra undici ProxyAgent, https-proxy-agent e socks-proxy-agent

Strumento Dipendenze Supporto SOCKS5 HTTP/2 Velocità
undici ProxyAgent no (integrato in Node 18+) no di default sì alta
https-proxy-agent 1-2 pacchetti no no media
socks-proxy-agent 3-4 pacchetti sì no media

Per il puro parsing HTTP/HTTPS con il minor numero di dipendenze, undici ProxyAgent vince grazie alla sua integrazione e compatibilità con HTTP/2. Per SOCKS5 in produzione, è più semplice utilizzare un collaudato socks-proxy-agent, piuttosto che mantenere un handshake personalizzato — l'implementazione manuale della sezione precedente è buona per l'apprendimento e casi edge rari, ma non per una produzione pesante con migliaia di connessioni all'ora.

Casi pratici: parsing e geo-testing

Monitoraggio dei prezzi sui marketplace. Uno script su undici con rotazione dei proxy e logica di retry delle sezioni precedenti è perfetto per aggirare il rate-limit su Wildberries e Ozon: ogni richiesta successiva della scheda prodotto viene inviata con un nuovo IP, e l'health-check esclude automaticamente gli indirizzi bannati dal pool.

Testing delle creatività pubblicitarie per geo. Se è necessario verificare come viene restituito il contenuto a un utente di un determinato paese per Google Ads o Facebook Ads, è sufficiente cambiare l'URL del proxy in ProxyAgent su un nodo della regione desiderata — non è necessario sollevare alcun browser se il controllo avviene tramite API o risposta HTML del server.

Verifica della disponibilità dei contenuti in diversi paesi. Per compiti in cui il sito restituisce contenuti diversi a seconda dell'IP, le connessioni residenziali appaiono per il server di destinazione indistinguibili da un normale utente, riducendo il rischio di blocco rispetto agli intervalli di IP di data center. Questo scenario è particolarmente rilevante per proxy mobili, se il servizio di destinazione verifica il tipo di rete e blocca espliciti sottoreti di data center.

Verifica massiva degli endpoint API. Durante il testing di carico della propria API tramite un pool di proxy esterno, è importante prestare attenzione al connection pooling — undici riutilizza per impostazione predefinita le connessioni TCP all'interno di un singolo ProxyAgent, il che fa risparmiare tempo durante le strette di mano TLS con centinaia di richieste consecutive a un singolo host.

Conclusione

L'undici ProxyAgent integrato copre la maggior parte delle esigenze con proxy HTTP/HTTPS in Node.js senza la necessità di portare pacchetti di terze parti: l'autenticazione tramite URL, il tunneling tramite CONNECT, la compatibilità con HTTP/2 e il connection pooling funzionano immediatamente. La rotazione e l'health-check dei proxy possono essere facilmente assemblati su promesse e timer puliti, mentre SOCKS5, se necessario, può essere implementato manualmente tramite un connettore connect personalizzato, anche se per la produzione è più ragionevole utilizzare una libreria pronta specificamente per SOCKS.

Se il tuo script su undici è destinato al parsing di marketplace o al monitoraggio dei prezzi, presta attenzione ai proxy residenziali — sono più resistenti ai sistemi anti-bot rispetto agli IP di data center classici e richiedono meno frequentemente una soluzione captcha aggiuntiva durante le richieste massicce a Wildberries, Ozon o Avito.