ProxynetProxynet

معادل cURL در JavaScript: fetch، Axios و پروکسی

تاریخ انتشار:

10 دقیقه مطالعه

Acar Diveroli
نویسنده: Acar Diveroli
نماد خط فرمان میان آکولاد

مستندات API و ابزارهای توسعه‌دهنده مرورگر اغلب درخواست نمونه را به شکل یک دستور cURL می‌دهند. برای یک آزمایش سریع در ترمینال ایده‌آل است؛ اما وقتی همان درخواست را باید به یک اپلیکیشن Node.js یا اسکریپت اسکرپینگ منتقل کنید، لازم است دستور را به JavaScript تبدیل کنید. در این نوشته معادل‌های JavaScript پرکاربردترین گزینه‌های cURL، نمونه‌های تبدیل، چهار روش استفاده از پروکسی در Node.js، مدیریت خطا و اشتباهات رایج را توضیح می‌دهیم.

همه نمونه‌ها روی Node.js 24 با undici 8 و Axios 1.20 آزموده شده‌اند.

معادل cURL در JavaScript چیست؟

دو گزینه اصلی وجود دارد:

  • fetch: در مرورگرها و از Node.js 18 به بعد به‌صورت درون‌ساخت موجود است. نیازی به بسته اضافه ندارد. پیاده‌سازی fetch در Node.js در پس‌زمینه از کتابخانه undici استفاده می‌کند.
  • Axios: کتابخانه‌ای محبوب و دیرینه که تبدیل خودکار JSON را انجام می‌دهد و پشتیبانی درخواست و پاسخ میانگیر (interceptor) ارائه می‌دهد.

استفاده دوباره از یک دستور cURL بدون خروج از ترمینال هم ممکن است (با child_process)، اما این روش مدیریت خطا و قابلیت انتقال را دشوار می‌کند؛ به نصب بودن cURL روی دستگاه وابسته می‌مانید. نوشتن با یک کلاینت بومی تقریباً همیشه بهتر است.

معادل‌های JavaScript گزینه‌های cURL

cURLfetchAxios
curl URLfetch(url)axios.get(url)
-X POSTmethod: "POST"axios.post(url, veri)
-H "Ad: Değer"headers: { Ad: "Değer" }headers: { Ad: "Değer" }
-d '{"a":1}'body: JSON.stringify({ a: 1 })به‌عنوان پارامتر دوم، یک شیء
-d "a=1&b=2"body: new URLSearchParams({ a: 1, b: 2 })new URLSearchParams(...) به‌عنوان پارامتر دوم
-F "dosya=@a.png"body: FormDataFormData به‌عنوان پارامتر دوم
-u kullanici:parolaهدر Authorization: "Basic ..."auth: { username, password }
-b "ad=deger" (کوکی)headers: { Cookie: "ad=deger" }headers: { Cookie: "ad=deger" }
-A "UA"headers: { "User-Agent": "UA" }headers: { "User-Agent": "UA" }
-L (پیگیری تغییرمسیر)به‌طور پیش‌فرض پیگیری می‌کندبه‌طور پیش‌فرض پیگیری می‌کند
-x proxydispatcher: new ProxyAgent(...)httpsAgent یا proxy
-m 20 (زمان پایان)signal: AbortSignal.timeout(20000)timeout: 20000
-i (هدرهای پاسخ)response.headersresponse.headers
-o dosyaبدنه را با fs.writeFile بنویسیدresponseType: "stream"

نمونه: تبدیل یک درخواست POST

این دستور cURL را در نظر بگیرید:

bash
curl -X POST https://httpbin.org/post \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer TOKEN" \
  -d '{"q": 1}'

با fetch

javascript
const response = await fetch("https://httpbin.org/post", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer TOKEN",
  },
  body: JSON.stringify({ q: 1 }),
  signal: AbortSignal.timeout(20000),
});

if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
console.log(data.json);

fetch ظرافتی دارد: وقتی سرور 404 یا 500 برمی‌گرداند خطا پرتاب نمی‌کند. باید خودتان موفق بودن پاسخ را با response.ok بررسی کنید. اما خطای شبکه (DNS، رد اتصال، پایان زمان) به‌صورت استثنا می‌آید.

با Axios

bash
npm install axios
javascript
import axios from "axios";

const { data } = await axios.post(
  "https://httpbin.org/post",
  { q: 1 },
  { headers: { Authorization: "Bearer TOKEN" }, timeout: 20000 },
);

console.log(data.json);

Axios خودش شیء را به JSON تبدیل می‌کند، هدر Content-Type را اضافه می‌کند و در پاسخ‌های خارج از محدوده 2xx خطا پرتاب می‌کند.

نمونه: ارسال فرم و بارگذاری فایل

ارسال فرمی که در cURL به شکل -d "a=1&b=2" است، در JavaScript با URLSearchParams انجام می‌شود:

javascript
const response = await fetch("https://httpbin.org/post", {
  method: "POST",
  body: new URLSearchParams({ a: "1", b: "2" }),
});
console.log((await response.json()).form);

وقتی بدنه URLSearchParams داده شود، fetch خودش هدر Content-Type را برابر application/x-www-form-urlencoded قرار می‌دهد. برای بارگذاری فایل (-F) از FormData استفاده می‌شود و مقدار مرز (boundary) هم خودکار اضافه می‌شود. یک نکته ظریف: اگر تابع fetch را از بسته undici می‌گیرید، کلاس FormData را هم از همان بسته بگیرید؛ FormData درون‌ساخت با fetch مربوط به undici هم‌خوان نیست و بدنه به‌صورت متن ساده ارسال می‌شود.

javascript
import { openAsBlob } from "node:fs";

const form = new FormData();
form.append("dosya", await openAsBlob("./a.png"), "a.png");

const response = await fetch("https://httpbin.org/post", { method: "POST", body: form });
console.log(Object.keys((await response.json()).files));

چهار روش استفاده از پروکسی در Node.js

در cURL، پروکسی فقط یک گزینه -x است. اما در Node.js روش بسته به اینکه کدام کلاینت را استفاده می‌کنید فرق دارد.

1. fetch و undici ProxyAgent

bash
npm install undici
javascript
import { fetch, ProxyAgent } from "undici";

const dispatcher = new ProxyAgent("http://kullanici:parola@pr.proxynet.io:8000");

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

توجه کنید که تابع fetch را هم از بسته undici درون‌ریزی کرده‌ایم. fetch درون‌ساخت Node.js هم بر پایه undici است، اما چون در بسته جداگانه‌ای که نصب می‌شود ممکن است تفاوت نسخه وجود داشته باشد، گرفتن هر دو از یک بسته امن‌ترین راه است.

2. تنظیم عمومی برای همه فراخوانی‌های fetch

اگر می‌خواهید هر فراخوانی fetch در اپلیکیشن از همان پروکسی استفاده کند، می‌توانید یک dispatcher عمومی تعریف کنید:

javascript
import { ProxyAgent, setGlobalDispatcher } from "undici";

setGlobalDispatcher(new ProxyAgent("http://kullanici:parola@pr.proxynet.io:8000"));

const response = await fetch("https://httpbin.org/ip");
console.log(await response.text());

این روش فراخوانی‌های fetch را که از درون یک کتابخانه شخص ثالث انجام می‌شوند هم پوشش می‌دهد؛ بدون دست زدن به کد منبع کتابخانه، ترافیک آن را از پروکسی عبور می‌دهید.

3. با متغیر محیطی، بدون تغییر کد

نسخه‌های جدید Node.js وقتی متغیر NODE_USE_ENV_PROXY فعال باشد، می‌توانند متغیرهای محیطی استاندارد پروکسی را برای fetch درون‌ساخت بخوانند:

bash
NODE_USE_ENV_PROXY=1 HTTPS_PROXY="http://kullanici:parola@pr.proxynet.io:8000" node uygulama.mjs

این روش برای عبور دادن یک اسکریپت موجود از پروکسی بدون دست زدن به کد منبع مفید است. متغیر NO_PROXY هم خوانده می‌شود؛ با افزودن آدرس‌های شبکه داخلی به این فهرست می‌توانید آن‌ها را خارج از پروکسی نگه دارید. اینکه این ویژگی در نسخه شما هست یا نه را از مستندات Node.js بررسی کنید.

4. Axios و مقصدهای HTTPS

گزینه درون‌ساخت proxy در Axios، برای آدرس‌های HTTP بدون رمزنگاری بی‌دردسر کار می‌کند:

javascript
const { data } = await axios.get("http://httpbin.org/ip", {
  proxy: {
    protocol: "http",
    host: "pr.proxynet.io",
    port: 8000,
    auth: { username: "kullanici", password: "parola" },
  },
});

برای آدرس‌های HTTPS اما استفاده از یک عامل پروکسی قابل‌اعتمادتر است:

bash
npm install https-proxy-agent
javascript
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

const httpsAgent = new HttpsProxyAgent("http://kullanici:parola@pr.proxynet.io:8000");

const { data } = await axios.get("https://httpbin.org/ip", { httpsAgent, proxy: false });
console.log(data);

خط proxy: false مهم است؛ منطق داخلی پروکسی Axios را خاموش می‌کند و کل تونل را به عامل واگذار می‌کند. اگر عامل را یک‌بار بسازید و با axios.create({ httpsAgent, proxy: false }) به یک نمونه متصل کنید، لازم نیست در هر فراخوانی دوباره بنویسید.

مقایسه چهار روش

روشدامنهتغییر کدمناسب چه حالتی
undici ProxyAgentیک فراخوانیdispatcher در هر فراخوانیپروکسی متفاوت برای درخواست‌های متفاوت
setGlobalDispatcherهمه فراخوانی‌های fetchیک خط، در ابتدایک پروکسی برای کل اپلیکیشن
NODE_USE_ENV_PROXYهمه فراخوانی‌های fetch درون‌ساختندارداسکریپت موجود، محیط استقرار
Axios + عاملنمونه AxioshttpsAgent + proxy: falseاگر پروژه از Axios استفاده می‌کند

تلاش مجدد و هم‌زمانی

در یک اسکریپت اسکرپینگ واقعی، درخواست‌ها گاهی شکست می‌خورند؛ ممکن است پروکسی به مقصد نرسد یا سایت 429 برگرداند. تابع کمکی زیر، درخواست ناموفق را با تأخیر نمایی دوباره تلاش می‌کند و شمار درخواست‌های باز هم‌زمان را محدود می‌کند:

javascript
import { fetch, ProxyAgent } from "undici";

const dispatcher = new ProxyAgent("http://kullanici:parola@pr.proxynet.io:8000");

async function getir(url, deneme = 3) {
  for (let i = 0; i < deneme; i++) {
    try {
      const r = await fetch(url, { dispatcher, signal: AbortSignal.timeout(20000) });
      if (r.status === 429 || r.status >= 500) throw new Error(`HTTP ${r.status}`);
      return await r.text();
    } catch (hata) {
      if (i === deneme - 1) throw hata;
      await new Promise((coz) => setTimeout(coz, 1000 * 2 ** i));
    }
  }
}

async function topluGetir(urls, esZamanli = 5) {
  const sonuclar = [];
  for (let i = 0; i < urls.length; i += esZamanli) {
    const grup = urls.slice(i, i + esZamanli);
    sonuclar.push(...(await Promise.allSettled(grup.map(getir))));
  }
  return sonuclar;
}

const urls = Array.from({ length: 20 }, (_, i) => `https://httpbin.org/get?i=${i}`);
const sonuclar = await topluGetir(urls);
console.log(sonuclar.filter((s) => s.status === "fulfilled").length, "başarılı");

Promise.allSettled مانع از این می‌شود که خطای یک درخواست باعث سقوط کل گروه شود؛ می‌توانید هر نتیجه را تک‌تک بررسی کنید. مدت انتظار در هر تلاش دو برابر می‌شود؛ اصرار با فاصله ثابت باعث سخت‌گیرتر شدن سایتی می‌شود که حد سرعت اعمال می‌کند.

اشتباهات رایج

کاراکتر خاص در رمز عبور

اگر در رمز عبور پروکسی شما کاراکترهایی مانند @، : یا / وجود دارد، آن‌ها را در آدرس با encodeURIComponent کدگذاری کنید. در غیر این صورت آدرس اشتباه تجزیه می‌شود و خطای 407 Proxy Authentication Required می‌گیرید.

درخواست‌های بدون زمان پایان

در تنظیمات پیش‌فرض یک درخواست ممکن است مدت طولانی منتظر پاسخ بماند و اسکریپت شما گیر کند. با AbortSignal.timeout در fetch و timeout در Axios، برای هر درخواست محدودیت زمانی بگذارید.

درخواست‌های زیاد به‌طور هم‌زمان

شروع صدها درخواست به‌طور همزمان با Promise.all هم محدودیت اتصال خودتان را زیر فشار می‌گذارد و هم باعث می‌شود در سایت مقصد به حد سرعت بخورید. هم‌زمانی را مانند بالا در گروه‌های کوچک محدود کنید. در کارهایی که تعداد زیادی درخواست ارسال می‌کنید، پروکسی چرخشی با پخش ترافیک میان IPهای مختلف ریسک مسدود شدن را کاهش می‌دهد.

دادن agent به fetch درون‌ساخت

در API قدیمی http.request در Node.js، پروکسی با گزینه agent داده می‌شود. fetch درون‌ساخت این گزینه را نمی‌شناسد و آن را بی‌سروصدا نادیده می‌گیرد؛ درخواست بدون پروکسی می‌رود. کلید درست برای fetch، dispatcher است.

کپی کردن هدرهای مرورگر بدون تغییر

درخواست‌های کپی‌شده از ابزارهای توسعه‌دهنده، هدرهای مخصوص مرورگر مانند sec-ch-ua، sec-fetch-* را حمل می‌کنند. در Node.js نیازی به ارسال آن‌ها نیست و برخی از آن‌ها ممکن است در سایت مقصد به‌عنوان ناسازگاری ارزیابی شوند؛ فقط هدرهایی را نگه دارید که واقعاً لازم‌اند.

صفحاتی که درخواست HTTP برایشان کافی نیست

fetch و Axios فقط HTML‌ای را می‌گیرند که سرور برمی‌گرداند. اگر محتوا بعداً با JavaScript در مرورگر بارگذاری می‌شود، آنچه دنبالش هستید را در صفحه پیدا نمی‌کنید. در این حالت یا باید درخواست API پس‌زمینه را پیدا کنید یا از یک اتوماسیون مرورگر استفاده کنید. گزینه‌ها را در اسکرپینگ وب: جاوااسکریپت یا پایتون؟ مقایسه کردیم؛ استفاده از پروکسی با Puppeteer را در Puppeteer و CAPTCHA نشان دادیم.

fetch یا Axios؟

  • fetch را انتخاب کنید: اگر وابستگی اضافه نمی‌خواهید، کد باید هم در مرورگر و هم در Node.js اجرا شود، یا می‌خواهید از ویژگی‌های مدیریت اتصال undici بهره ببرید.
  • Axios را انتخاب کنید: اگر به راحتی‌هایی مانند تبدیل خودکار JSON، interceptorها و پرتاب خطا در پاسخ‌های خارج از 2xx نیاز دارید یا از قبل در پروژه استفاده می‌شود.

تفاوت میان این دو بیشتر به عادت مربوط است تا کارکرد. شروع یک پروژه جدید با fetch، تعداد وابستگی‌ها را پایین نگه می‌دارد؛ در یک پروژه بزرگ Axios هم دلیلی برای رفتن به fetch وجود ندارد.

سؤالات متداول

آیا می‌توانم دستور cURL را خودکار به JavaScript تبدیل کنم؟

در ابزارهای توسعه‌دهنده مرورگر، از تب Network می‌توانید روی یک درخواست راست‌کلیک کنید و با گزینه «Copy as fetch» مستقیماً آن را به‌صورت کد fetch کپی کنید. با این حال کد به‌دست‌آمده را بازبینی کنید؛ هدرهای مخصوص مرورگر ممکن است در Node.js غیرضروری باشند.

آیا می‌توانم در Node.js از پروکسی SOCKS5 استفاده کنم؟

fetch درون‌ساخت و undici ProxyAgent برای پروکسی HTTP هستند. برای SOCKS5 باید با بسته‌ای اضافه مانند socks-proxy-agent عامل تعریف کنید. در سمت cURL، شمای socks5h:// مستقیماً پشتیبانی می‌شود؛ جزئیات در چگونه با cURL از پروکسی استفاده کنیم؟.

آیا در JavaScript سمت مرورگر می‌توان پروکسی تعریف کرد؟

خیر. fetch در مرورگر اجازه نمی‌دهد تنظیم پروکسی را از راه کد تغییر دهید؛ پروکسی در تنظیمات مرورگر یا سیستم‌عامل تعریف می‌شود. مدیریت پروکسی از درون کد مخصوص محیط‌های سروری مانند Node.js است. برای پروکسی در سطح مرورگر از افزونه‌هایی مانند SwitchyOmega استفاده می‌شود.

چگونه کوکی‌ها را بین درخواست‌ها حفظ کنم؟

fetch درون‌ساخت کوکی ذخیره نمی‌کند. باید هدر Set-Cookie را از پاسخ بخوانید و به‌عنوان هدر Cookie به درخواست بعدی اضافه کنید؛ یا با Axios از یک ظرف کوکی مبتنی بر tough-cookie استفاده کنید. فراموش نکنید که در جریان‌های ورود، IP هم باید ثابت بماند؛ پروکسی با نشست ثابت برای همین کار طراحی شده است.

آیا می‌توانم در هر درخواست پروکسی متفاوتی استفاده کنم؟

بله. با undici می‌توانید به هر فراخوانی یک ProxyAgent متفاوت بدهید. اما به‌جای مدیریت خودتان فهرست، استفاده از پروکسی چرخشی ساده‌تر است: یک آدرس، در هر اتصال IP خروجی متفاوت.

آیا همین کد در TypeScript کار می‌کند؟

بله. undici و Axios با تعریف‌های تایپ عرضه می‌شوند؛ برای گزینه dispatcher باید از نوع fetch خود undici استفاده کنید، چون تعریف تایپ fetch درون‌ساخت این گزینه را نمی‌شناسد.

خلاصه

دستورهای cURL در JavaScript دقیقاً با fetch یا Axios قابل جایگزینی هستند. برای پروکسی در Node.js چهار روش وجود دارد: undici ProxyAgent، dispatcher عمومی، متغیر محیطی NODE_USE_ENV_PROXY و عامل پروکسی برای Axios. افزودن زمان پایان، بررسی خطا، تلاش مجدد با تأخیر نمایی و محدودیت هم‌زمانی از همان ابتدا، اسکریپت‌های شما را بسیار قابل‌اعتمادتر می‌کند. بسته‌های مناسب برای ترافیک وب را در صفحه پروکسی HTTPS ما پیدا می‌کنید.

پرسش از ChatGPTپرسش از Claude