Sebagian besar panduan tentang proxy di Node.js menyarankan untuk menambahkan https-proxy-agent, socks-proxy-agent, dan beberapa paket lainnya. Namun, sejak versi Node.js 18+, klien HTTP bawaan undici sudah menyertakan ProxyAgent, yang menangani sebagian besar tugas parsing dan pengujian API melalui proxy. Dalam artikel ini, kita akan membahas cara mengatur tunneling HTTP/HTTPS melalui undici, mengatur rotasi IP di antara permintaan, dan apa yang harus dilakukan jika SOCKS5 diperlukan tanpa menambahkan ketergantungan tambahan.
Mengapa undici daripada axios dan request
undici adalah klien HTTP/1.1 dan HTTP/2 yang ditulis oleh tim Node.js dan sejak versi 18 menjadi dasar dari fetch global. Ia tidak memiliki ketergantungan transitif, lebih cepat daripada axios dalam sebagian besar benchmark pada connection pooling, dan yang penting untuk topik kita — ia memiliki modul bawaan untuk bekerja dengan proxy melalui tunneling HTTP CONNECT.
Ketika Anda melakukan parsing Wildberries, Ozon, atau menguji kreatif iklan dari berbagai geo, setiap paket tambahan di node_modules adalah titik kegagalan potensial saat memperbarui Node.js. undici disertakan dengan runtime, sehingga Anda mendapatkan implementasi TLS, HTTP/2, dan keep-alive yang mutakhir tanpa risiko bahwa pustaka yang ditinggalkan akan rusak pada versi mayor baru.
Instalasi sangat sederhana:
npm install undici
Jika Anda menggunakan Node.js 20+, sebagian API tersedia secara global tanpa impor, tetapi untuk ProxyAgent, impor tetap diperlukan karena tidak diekspor ke fetch global secara default.
Pengaturan Dasar ProxyAgent
Contoh kerja minimal untuk permintaan melalui proxy terlihat seperti ini:
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);
Poin kunci adalah parameter dispatcher. Ini bukan opsi agent, seperti di modul lama http, tetapi dispatcher undici yang menangkap koneksi sebelum TLS handshake dan mengatur tunnel melalui metode CONNECT ke host tujuan. Untuk permintaan HTTPS melalui proxy, ini sangat penting: proxy HTTP biasa tidak melihat konten lalu lintas terenkripsi, ia hanya meneruskan byte di dalam tunnel TCP.
Jika Anda bekerja bukan dengan fetch, tetapi dengan undici.request yang lebih rendah, sintaksisnya serupa:
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());
}
Untuk parser produksi yang melakukan ratusan permintaan per menit, penting untuk tidak membuat ProxyAgent baru untuk setiap permintaan — ini merusak connection pooling. Buatlah instans agen satu kali untuk pool koneksi dan gunakan kembali di antara panggilan.
Otentikasi proxy dengan username dan password
Sebagian besar penyedia proxy berbayar memberikan akses dengan pasangan username/password, bukan dengan IP-whitelisting. Di undici, kredensial dikirim langsung di URL 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());
Perhatikan: jika username atau password mengandung karakter khusus (@, :, /), mereka perlu di-encode menggunakan encodeURIComponent, jika tidak, parser URL di dalam undici tidak akan dapat memparse string koneksi dengan benar dan Anda akan mendapatkan kesalahan otentikasi 407 alih-alih pesan yang jelas.
Untuk tugas di mana stabilitas sesi penting — misalnya, otorisasi di marketplace yang terikat pada satu IP selama sesi — lebih baik menggunakan proxy residensial dengan sesi lengket: IP tidak berubah untuk waktu yang ditentukan, sehingga layanan tidak melihat "loncatan" antara geo dan tidak memerlukan captcha ulang di setiap permintaan.
Rotasi proxy di antara permintaan
Jika Anda memiliki daftar N proxy dan tugasnya adalah mendistribusikan permintaan di antara mereka, opsi paling sederhana adalah round-robin tanpa pustaka pihak ketiga untuk antrian:
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());
}
Pendekatan ini bekerja dengan baik untuk parsing kartu produk atau pemantauan harga, ketika setiap permintaan berikutnya secara logis independen dari yang sebelumnya. Untuk tugas yang memerlukan kecepatan tinggi dalam mengganti IP di setiap permintaan tanpa pengelolaan manual daftar, lebih mudah menggunakan proxy dengan rotasi otomatis di sisi penyedia — maka seluruh kode di atas dapat disederhanakan menjadi satu URL gateway statis, dan rotasi ditangani oleh proxy data center atau pool residensial dengan rotasi berdasarkan timer.
Jika Anda memerlukan rotasi tidak hanya secara melingkar, tetapi berbobot — misalnya, memberikan prioritas pada proxy yang lebih cepat — tambahkan bobot ke struktur data:
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;
}
Pemeriksaan kesehatan proxy dan pengecualian IP "mati"
Pool proxy seiring waktu "kadaluarsa": beberapa IP diblokir oleh situs target, beberapa berhenti merespons. Agar rotasi tidak mengirim permintaan melalui alamat mati, tambahkan pemeriksaan kesehatan dengan timeout menggunakan AbortController bawaan:
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);
}
Jalankan pemeriksaan seperti ini setiap beberapa menit dengan timer latar belakang dan perbarui pool aktif, bukan seluruh daftar statis. Ini sangat penting jika Anda memiliki proxy sendiri di IP data center murah — di sana persentase kegagalan lebih tinggi dibandingkan dengan pool yang dikelola oleh penyedia.
SOCKS5 tanpa pustaka pihak ketiga
Catatan penting: ProxyAgent bawaan di undici bekerja dengan tunneling HTTP CONNECT dan "secara default" hanya memahami proxy HTTP/HTTPS. Protokol SOCKS5 berfungsi secara berbeda — ini adalah handshake biner pada tingkat TCP, dan undici tidak memparse-nya secara native. Jika Anda sangat memerlukan SOCKS5 tanpa menginstal socks-proxy-agent, Anda dapat mengimplementasikan konektor minimal secara manual melalui modul net dan opsi connect di Agent kustom:
import net from 'node:net';
import { Agent, fetch } from 'undici';
function socksHandshake(socket, targetHost, targetPort) {
return new Promise((resolve, reject) => {
// Langkah 1: sapaan, metode tanpa otentikasi
socket.write(Buffer.from([0x05, 0x01, 0x00]));
socket.once('data', (greeting) => {
if (greeting[1] !== 0x00) {
return reject(new Error('SOCKS5: metode otentikasi tidak didukung'));
}
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: koneksi ditolak'));
}
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());
Ini adalah handshake yang disederhanakan tanpa otentikasi menggunakan username dan password (metode 0x02) dan tanpa dukungan IPv6 — untuk produksi, sebaiknya tambahkan cabang dengan otentikasi dan penanganan berbagai jenis alamat. Namun untuk memahami mekanisme — ini sudah cukup: SOCKS5 tidak memerlukan TLS di dalam tunnel pada tahap handshake, berbeda dengan HTTP CONNECT, sehingga kode ini lebih pendek dari yang terlihat pada pandangan pertama.
Dalam praktiknya, SOCKS5 lebih sering digunakan bukan untuk parsing halaman web, tetapi untuk meneruskan lalu lintas aplikasi seluler dan browser anti-detect seperti Dolphin Anty atau AdsPower, di mana protokol diperlukan untuk beroperasi pada tingkat soket, bukan permintaan HTTP. Jika tugas Anda adalah lalu lintas HTTP/HTTPS di Node.js, ProxyAgent bawaan biasanya sudah cukup, dan tidak ada gunanya repot-repot dengan konektor SOCKS5 manual.
Penanganan timeout dan logika retry
Proxy sering memutuskan koneksi lebih sering daripada koneksi langsung — ini normal untuk jaringan residensial, di mana node dapat offline kapan saja. Anda memerlukan pembungkus retry dengan penundaan eksponensial:
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(`Kesalahan 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(`Semua upaya telah habis: ${lastError?.message}`);
}
Pembungkus seperti ini juga menyelesaikan masalah rotasi saat terjadi kesalahan: jika proxy saat ini gagal, upaya berikutnya secara otomatis beralih ke IP lain dari daftar, bukan terus-menerus mencoba node mati yang sama.
Perbandingan undici ProxyAgent, https-proxy-agent, dan socks-proxy-agent
| Alat | Ketergantungan | Dukungan SOCKS5 | HTTP/2 | Kecepatan |
|---|---|---|---|---|
| undici ProxyAgent | tidak ada (bawaan di Node 18+) | tidak secara default | ya | tinggi |
| https-proxy-agent | 1-2 paket | tidak | tidak | sedang |
| socks-proxy-agent | 3-4 paket | ya | tidak | sedang |
Untuk parsing HTTP/HTTPS yang bersih dengan jumlah ketergantungan minimal, undici ProxyAgent unggul karena keterpaduan dan kompatibilitas dengan HTTP/2. Untuk SOCKS5 dalam produksi, lebih mudah menggunakan socks-proxy-agent yang sudah teruji daripada mempertahankan handshake yang ditulis sendiri — implementasi manual dari bagian di atas baik untuk pembelajaran dan kasus tepi yang jarang, tetapi tidak untuk produksi yang padat dengan ribuan koneksi per jam.
Kasus Praktis: parsing dan pengujian geo
Pemantauan harga di marketplace. Skrip di undici dengan rotasi proxy dan logika retry dari bagian di atas sangat cocok untuk menghindari rate-limit di Wildberries dan Ozon: setiap permintaan berikutnya untuk kartu produk dikirim dari IP baru, dan pemeriksaan kesehatan secara otomatis mengecualikan alamat yang diblokir dari pool.
Penguji kreatif iklan berdasarkan geo. Jika Anda perlu memeriksa bagaimana konten disajikan kepada pengguna dari negara tertentu untuk Google Ads atau Facebook Ads, cukup ganti URL proxy di ProxyAgent ke node di wilayah yang diinginkan — tidak perlu mengangkat browser jika pemeriksaan dilakukan melalui API atau respons HTML dari server.
Pemeriksaan ketersediaan konten di berbagai negara. Untuk tugas di mana situs memberikan konten yang berbeda tergantung pada IP, koneksi residensial tampak tidak berbeda bagi server target dibandingkan dengan pengguna biasa, yang mengurangi risiko pemblokiran dibandingkan dengan rentang IP data center. Skenario ini sangat relevan untuk proxy seluler, jika layanan target memeriksa jenis jaringan dan memblokir subnet data center yang jelas.
Pemeriksaan massal endpoint API. Saat melakukan pengujian beban pada API Anda sendiri melalui pool proxy eksternal, penting untuk memperhatikan connection pooling — undici secara default menggunakan kembali koneksi TCP di dalam satu ProxyAgent, yang secara signifikan menghemat waktu pada TLS handshake saat melakukan ratusan permintaan berturut-turut ke satu host.
Kesimpulan
undici ProxyAgent bawaan menangani sebagian besar tugas dengan proxy HTTP/HTTPS di Node.js tanpa perlu menambahkan paket pihak ketiga: otentikasi melalui URL, tunneling melalui CONNECT, kompatibilitas dengan HTTP/2, dan connection pooling bekerja langsung. Rotasi dan pemeriksaan kesehatan proxy mudah dibangun menggunakan promise dan timer murni, dan SOCKS5 jika diperlukan dapat diimplementasikan secara manual melalui konektor connect kustom, meskipun untuk produksi lebih bijaksana menggunakan pustaka yang sudah ada khusus untuk SOCKS.
Jika skrip Anda di undici ditujukan untuk parsing marketplace atau pemantauan harga, perhatikan proxy residensial — mereka lebih tahan terhadap sistem anti-bot dibandingkan dengan IP data center klasik, dan jarang memerlukan solusi captcha tambahan saat melakukan permintaan massal ke Wildberries, Ozon, atau Avito.