Использование прокси в Deno и Bun — зачем и как
Если вы когда-либо пытались выполнить HTTP-запрос через прокси в современном JavaScript-рантайме, вы наверняка столкнулись с сюрпризом: fetch() игнорирует прокси по умолчанию. Ни Deno, ни Bun не подхватывают настройки прокси автоматически — в отличие от Node.js с его экосистемой https-proxy-agent. Использование прокси в Deno и Bun требует явной конфигурации, но зато даёт точный контроль над каждым запросом.
Это руководство показывает, как настроить Deno прокси через Deno.createHttpClient и Bun fetch прокси через встроенную опцию proxy, с runnable-примерами, geo-targeting, sticky-сессиями и production-паттернами. Все примеры используют шлюз ProxyHat gate.proxyhat.com:8080 (HTTP) и gate.proxyhat.com:1080 (SOCKS5).
Почему fetch() игнорирует прокси по умолчанию
Стандарт fetch(), определённый MDN Web Docs, не включает параметр прокси в сигнатуру запроса. Это намеренное решение: fetch спроектирован для браузерного контекста, где прокси управляются на уровне ОС или браузера. Серверные рантаймы наследуют этот интерфейс, но добавляют собственные расширения.
Deno решает это через Deno.createHttpClient() — фабрику, возвращающую объект клиента, который передаётся в fetch(url, { client }). Bun идёт другим путём: добавляет опцию proxy прямо в сигнатуру fetch(url, { proxy }). Оба подхода работают, но имеют разные trade-off'ы.
| Аспект | Deno | Bun |
|---|---|---|
| Метод настройки прокси | Deno.createHttpClient({ proxy: { url, basicAuth } }) | fetch(url, { proxy: 'http://user:pass@host:port' }) |
| Гранулярность | Per-client (переиспользуемый объект) | Per-request (строка в опциях) |
| SOCKS5 поддержка | Через URL-схему socks5:// | Через URL-схему socks5:// |
| CA-сертификаты | caCerts в createHttpClient | Переменные окружения |
| Connection reuse | Автоматически через клиент | Автоматически через пул |
Настройка Deno прокси через Deno.createHttpClient
Согласно официальной документации Deno, Deno.createHttpClient принимает объект с полем proxy, содержащим url и опционально basicAuth. Полученный клиент передаётся в fetch через опцию client.
// deno run --allow-net proxy_deno.ts
const proxyUrl = "http://gate.proxyhat.com:8080";
const username = "user-country-US";
const password = "your-password";
// Создаём HTTP-клиент с прокси
const client = Deno.createHttpClient({
proxy: {
url: proxyUrl,
basicAuth: {
username,
password,
},
},
});
// Используем клиент в fetch
const response = await fetch("https://httpbin.org/ip", {
client,
});
const data = await response.json();
console.log("IP через прокси:", data.origin);
Ключевое преимущество подхода Deno — переиспользование клиента. Создав client один раз, вы можете использовать его в десятках запросов, и Deno будет поддерживать keep-alive соединения. Это снижает latency на ~200ms при повторных запросах.
Переменные окружения в Deno
Deno также поддерживает HTTP_PROXY и HTTPS_PROXY через флаг --allow-env, но per-client конфигурация предпочтительна, когда вам нужны разные прокси для разных задач:
// Разные клиенты для разных geo-таргетов
const usClient = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: "user-country-US", password: "pass" },
},
});
const deClient = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: "user-country-DE", password: "pass" },
},
});
const [usRes, deRes] = await Promise.all([
fetch("https://httpbin.org/ip", { client: usClient }),
fetch("https://httpbin.org/ip", { client: deClient }),
]);
console.log("US IP:", (await usRes.json()).origin);
console.log("DE IP:", (await deRes.json()).origin);
Настройка Bun fetch прокси
Bun, согласно документации Bun, добавляет опцию proxy прямо в fetch. Это проще в использовании, но менее гибко — нет переиспользуемого клиента с общим пулом соединений.
// bun run proxy_bun.ts
const proxyString = "http://user-country-US:your-password@gate.proxyhat.com:8080";
const response = await fetch("https://httpbin.org/ip", {
proxy: proxyString,
});
const data = await response.json();
console.log("IP через прокси:", data.origin);
Для прокси fetch JavaScript в Bun это всё, что нужно — одна строка. Но если вы делаете много запросов, создавайте вспомогательную функцию:
// bun run proxy_bun_helper.ts
function createProxyUrl(opts: {
country?: string;
city?: string;
session?: string;
password: string;
}): string {
let username = "user";
if (opts.country) username += `-country-${opts.country}`;
if (opts.city) username += `-city-${opts.city}`;
if (opts.session) username += `-session-${opts.session}`;
return `http://${username}:${opts.password}@gate.proxyhat.com:8080`;
}
const password = "your-password";
// Sticky-сессия для последовательных запросов
const stickyProxy = createProxyUrl({
country: "US",
session: "order-check-001",
password,
});
const res1 = await fetch("https://httpbin.org/ip", { proxy: stickyProxy });
const res2 = await fetch("https://httpbin.org/headers", { proxy: stickyProxy });
console.log("IP 1:", (await res1.json()).origin);
console.log("IP 2:", (await res2.json()).origin); // Тот же IP
Geo-targeting и sticky-сессии в username
ProxyHat кодирует geo-таргетинг и sticky-сессии прямо в username. Формат: user-country-{CC}-city-{city}-session-{id}. Это работает одинаково для HTTP (порт 8080) и SOCKS5 (порт 1080).
// deno run --allow-net geo_sessions.ts (или bun run geo_sessions.ts)
const PASSWORD = "your-password";
// US, Нью-Йорк, sticky-сессия
const usNyProxy = "http://user-country-US-city-newyork-session-abc123:" + PASSWORD + "@gate.proxyhat.com:8080";
// Германия, Берлин, rotating (без session)
const deProxy = "http://user-country-DE-city-berlin:" + PASSWORD + "@gate.proxyhat.com:8080";
// Япония, Токио, sticky-сессия для скрапинга SERP
const jpProxy = "http://user-country-JP-city-tokyo-session-serp-42:" + PASSWORD + "@gate.proxyhat.com:8080";
console.log("US NY:", usNyProxy);
console.log("DE Berlin:", deProxy);
console.log("JP Tokyo:", jpProxy);
Sticky-сессии сохраняют один IP на протяжении всей сессии — это критично для сайтов с stateful-авторизацией (корзины, многошаговые формы). Время жизни sticky-сессии зависит от тарифа; подробности на странице тарифов ProxyHat.
SOCKS5 на порту 1080
Для задач, где нужен TCP-уровень (например, обход HTTP-фильтрации), ProxyHat поддерживает SOCKS5 на порту 1080. В Deno и Bun SOCKS5 указывается через URL-схему socks5://.
// Deno SOCKS5
const socksClient = Deno.createHttpClient({
proxy: {
url: "socks5://gate.proxyhat.com:1080",
basicAuth: {
username: "user-country-GB-session-socks-1",
password: "your-password",
},
},
});
const res = await fetch("https://httpbin.org/ip", { client: socksClient });
console.log("SOCKS5 IP:", (await res.json()).origin);
// Bun SOCKS5
const socksProxy = "socks5://user-country-GB-session-socks-1:your-password@gate.proxyhat.com:1080";
const bunRes = await fetch("https://httpbin.org/ip", { proxy: socksProxy });
console.log("Bun SOCKS5 IP:", (await bunRes.json()).origin);
Переменные окружения HTTP_PROXY и HTTPS_PROXY
Оба рантайма поддерживают переменные окружения HTTP_PROXY и HTTPS_PROXY. Это удобно для quick-and-dirty скриптов, но per-client конфигурация предпочтительна в production:
- Переменные окружения глобальны — все запросы идут через один прокси, даже те, которым это не нужно.
- Нельзя задать разные geo-таргеты для разных частей приложения.
- Сложно отлаживать: прокси «невидим» в коде.
- Невозможно управлять sticky-сессиями — каждый запрос получает новый IP.
# Установка через переменные окружения (оба рантайма)
export HTTP_PROXY="http://user-country-US:your-password@gate.proxyhat.com:8080"
export HTTPS_PROXY="http://user-country-US:your-password@gate.proxyhat.com:8080"
# Deno: теперь fetch подхватит прокси автоматически
deno run --allow-net --allow-env script.ts
# Bun: аналогично
bun run script.ts
Residential-прокси для целей с высокой защитой
Datacenter-прокси часто блокируются антибот-системами, потому что их IP-диапазоны известны и помечены как «hosting provider». Residential-прокси используют IP реальных устройств, что делает их неотличимыми от обычных пользователей. Это критично для SERP-скрапинга, ценового мониторинга и сбора данных с защищённых сайтов. Подробнее — на странице use-case веб-скрапинга.
Следующий пример показывает ротацию пула sticky-сессий с concurrent-запросами через Promise.all и AbortController для таймаута:
// deno run --allow-net concurrent_pool.ts
// или: bun run concurrent_pool.ts
const PASSWORD = "your-password";
const TARGET = "https://httpbin.org/ip";
const TIMEOUT_MS = 10_000;
const POOL_SIZE = 10;
interface FetchResult {
session: string;
ip: string | null;
status: number | null;
error: string | null;
}
async function fetchWithProxy(sessionId: string): Promise<FetchResult> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
const proxyUrl = `http://user-country-US-session-${sessionId}:${PASSWORD}@gate.proxyhat.com:8080`;
try {
// Для Deno — используем createHttpClient; для Bun — опцию proxy в fetch
const isDeno = typeof Deno !== "undefined";
let res: Response;
if (isDeno) {
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: `user-country-US-session-${sessionId}`,
password: PASSWORD,
},
},
});
res = await fetch(TARGET, { client, signal: controller.signal });
} else {
res = await fetch(TARGET, {
proxy: proxyUrl,
signal: controller.signal,
});
}
const data = await res.json();
return { session: sessionId, ip: data.origin, status: res.status, error: null };
} catch (err) {
return {
session: sessionId,
ip: null,
status: null,
error: err instanceof Error ? err.message : String(err),
};
} finally {
clearTimeout(timeout);
}
}
// Запускаем 10 concurrent-запросов с разными sticky-сессиями
const sessions = Array.from({ length: POOL_SIZE }, (_, i) => `pool-${i}`);
const results = await Promise.all(sessions.map(fetchWithProxy));
const successCount = results.filter((r) => r.error === null).length;
console.log(`Успешно: ${successCount}/${POOL_SIZE}`);
for (const r of results) {
console.log(` Сессия ${r.session}: IP=${r.ip}, status=${r.status}, error=${r.error}`);
}
Этот паттерн даёт 100 concurrent sessions с уникальными IP, каждая со своим sticky-идентификатором. Если сайт блокирует один IP — остальные продолжают работать. Таймаут через AbortController предотвращает зависшие запросы.
Production-советы: ретраи, CA-сертификаты, connection reuse
Ретраи с экспоненциальным backoff
// Универсальный retry-обёртка для Deno и Bun
const MAX_RETRIES = 3;
const BASE_DELAY_MS = 500;
async function fetchWithRetry(
url: string,
proxyConfig: { url: string; basicAuth: { username: string; password: string } },
): Promise<Response> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
const isDeno = typeof Deno !== "undefined";
if (isDeno) {
const client = Deno.createHttpClient({ proxy: proxyConfig });
const res = await fetch(url, { client });
if (res.status === 429 || res.status >= 500) {
throw new Error(`HTTP ${res.status}`);
}
return res;
} else {
const proxyStr = `http://${proxyConfig.basicAuth.username}:${proxyConfig.basicAuth.password}@${proxyConfig.url.replace("http://", "")}`;
const res = await fetch(url, { proxy: proxyStr });
if (res.status === 429 || res.status >= 500) {
throw new Error(`HTTP ${res.status}`);
}
return res;
}
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 100;
console.warn(`Попытка ${attempt + 1} не удалась: ${lastError.message}. Ждём ${Math.round(delay)}ms`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw lastError ?? new Error("Неизвестная ошибка");
}
// Использование
const res = await fetchWithRetry("https://httpbin.org/ip", {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: "user-country-FR-session-retry-1", password: "pass" },
});
console.log(await res.json());
Custom CA через Deno caCerts
Deno позволяет передать CA-сертификаты напрямую в createHttpClient — полезно для корпоративных сред с MITM-прокси:
// deno run --allow-net --read ca_certs.ts
const caCert = await Deno.readTextFile("./custom-ca.pem");
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: "user-country-US", password: "pass" },
},
caCerts: [caCert],
});
const res = await fetch("https://httpbin.org/ip", { client });
console.log(await res.json());
ProxyHat Node SDK: сравнение side-by-side
ProxyHat предоставляет Node SDK, который работает в обоих рантаймах — Deno и Bun поддерживают npm-пакеты. SDK абстрагирует управление прокси, ротацию и обработку ошибок. Документация доступна на docs.proxyhat.com.
// Установка: npm install @proxyhat/node-sdk (или deno add / bun add)
// deno run --allow-net sdk_example.ts (или bun run sdk_example.ts)
import { ProxyHat } from "@proxyhat/node-sdk";
const ph = new ProxyHat({
username: "your-username",
password: "your-password",
defaultCountry: "US",
});
// Простой запрос
const res = await ph.fetch("https://httpbin.org/ip");
console.log("SDK IP:", (await res.json()).origin);
// С geo-targeting и sticky-сессией
const res2 = await ph.fetch("https://httpbin.org/ip", {
country: "DE",
city: "berlin",
session: "checkout-flow-42",
});
console.log("DE Berlin IP:", (await res2.json()).origin);
// Concurrent-запросы с автоматической ротацией
const urls = [
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
];
const results = await Promise.all(
urls.map((url) => ph.fetch(url, { country: "JP", rotate: true })),
);
for (const r of results) {
console.log(r.status, await r.text());
}
| Подход | Строк кода | Контроль | Абстракция |
|---|---|---|---|
| Raw fetch + Deno.createHttpClient | ~10 | Полный | Низкая |
| Raw fetch + Bun proxy | ~5 | Полный | Низкая |
| ProxyHat Node SDK | ~3 | Средний | Высокая |
SDK удобен для быстрого старта, но raw-подход даёт больше контроля — например, для custom CA-сертификатов или нестандартных таймаутов. Узнать больше о доступных локациях можно на странице локаций ProxyHat.
Этический скрапинг и правовые аспекты
Использование прокси — это инструмент, а не лицензия на сбор любых данных. Несколько принципов:
- Сначала проверьте официальный API. Многие платформы (Google, Amazon, Twitter/X) предлагают API с легальным доступом к данным. Если API покрывает ваши нужды — используйте его.
- Публичные данные только. В США Computer Fraud and Abuse Act (CFAA) регулирует доступ к компьютерным системам. Скрапинг публично доступных данных (без авторизации) обычно законен, но обход paywall или access control может нарушать CFAA.
- GDPR в ЕС. Сбор персональных данных граждан ЕС требует правового основания. Прокси не делают сбор данных легальным — они лишь скрывают ваш IP.
- Соблюдайте robots.txt. Это не закон, но индустриальный стандарт. Уважайте
Crawl-delayиDisallowдирективы. - Rate limiting. Даже с 100 concurrent sessions, не перегружайте целевой сервер. Адекватный rate limit — 1–5 запросов в секунду на домен.
Для SERP-скрапинга и ценового мониторинга residential-прокси — стандартный инструмент. Подробнее — на странице SERP tracking.
Key Takeaways
- fetch() не поддерживает прокси по умолчанию — ни в Deno, ни в Bun. Нужна явная конфигурация.
- Deno: используйте
Deno.createHttpClient({ proxy: { url, basicAuth } })и передавайте клиент вfetch(url, { client }).- Bun: используйте
fetch(url, { proxy: 'http://user:pass@gate.proxyhat.com:8080' })— одна строка.- Geo-targeting и sticky-сессии кодируются в username:
user-country-US-session-abc123.- SOCKS5 доступен на порту 1080 через URL-схему
socks5://.- Per-client конфигурация предпочтительнее
HTTP_PROXYenv-var в production — больше контроля и гибкости.- Residential-прокси необходимы для сайтов с антибот-защитой; datacenter-IP часто блокируются.
- Ретраи с backoff,
AbortControllerтаймауты и connection reuse — обязательны в production.- ProxyHat Node SDK работает в обоих рантаймах и упрощает ротацию и geo-targeting.
- Этика важна: сначала официальный API, публичные данные только, уважайте robots.txt и GDPR.






