Использование прокси в Deno и Bun: руководство для разработчиков

Практическое руководство по настройке прокси в современных JavaScript-рантаймах Deno и Bun: от базового fetch до ротации sticky-сессий с AbortController и production-паттернов.

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

Использование прокси в Deno и Bun — это задача, с которой сталкивается каждый разработчик, запускающий веб-скрейпинг, автоматизацию или ценовой мониторинг на новых JavaScript-рантаймах. В отличие от Node.js, где прокси-агенты давно стандартизированы через https-proxy-agent и подобные пакеты, Deno и Bun решают вопрос иначе: встроенный fetch() по умолчанию игнорирует системные прокси, и конфигурация задаётся на уровне клиента или вызова.

Почему fetch игнорирует прокси и как это решают Deno и Bun

Стандарт WHATWG Fetch не описывает прокси-параметров — это намеренное решение, оставляющее прокси-конфигурацию на усмотрение среды выполнения. В браузере прокси управляется ОС или расширениями; в серверных рантаймах — разработчиком. Это означает, что fetch('https://example.com') в Deno и Bun всегда идёт напрямую, если вы не указали прокси явно.

Deno решает это через Deno.createHttpClient() — фабрику HTTP-клиентов, принимающую объект с полем proxy. Полученный клиент передаётся в fetch() как опция { client }. Bun идёт проще: прокси указывается прямо в опциях вызова fetch(url, { proxy }), без отдельного клиентского объекта.

Deno: Deno.createHttpClient с прокси

// deno run --allow-net proxy_demo.ts

const client = Deno.createHttpClient({
  proxy: {
    url: "http://gate.proxyhat.com:8080",
    basicAuth: {
      username: "user-country-US",
      password: "YOUR_PASSWORD",
    },
  },
});

const res = await fetch("https://httpbin.org/ip", { client });
const body = await res.text();
console.log(body);
// { "origin": "198.51.100.42" } — IP адрес прокси

Обратите внимание: basicAuth — отдельное поле, а не часть URL. Это удобно, когда учётные данные содержат специальные символы, которые пришлось бы URL-кодировать.

Bun: fetch с опцией proxy

// bun run proxy_demo.ts

const res = await fetch("https://httpbin.org/ip", {
  proxy: "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080",
});

const body = await res.text();
console.log(body);

Bun принимает прокси как строку URL в одной строке — проще для быстрых скриптов, но менее гибко при ротации. Для динамической смены прокси на каждый запрос достаточно собирать строку заново.

Геотаргетинг и sticky-сессии в имени пользователя

ProxyHat кодирует параметры сессии и геолокации прямо в имени пользователя. Это работает с любым рантаймом — Deno, Bun, Node.js, curl — потому что всё сводится к HTTP Basic Auth.

ПараметрФормат в usernameПример
Странаuser-country-XXuser-country-DE
Городuser-country-XX-city-nameuser-country-DE-city-berlin
Sticky-сессияuser-session-IDuser-session-abc123
Комбинацияuser-country-US-session-abc123США + сессия abc123

Sticky-сессия сохраняет один выходной IP на протяжении жизни сессии — обычно до 10–30 минут в зависимости от провайдера. Это критично для сайтов, требующих последовательности запросов: вход в аккаунт, навигация по каталогу, добавление в корзину.

SOCKS5 на порту 1080

// Deno — SOCKS5 через createHttpClient

const socksClient = Deno.createHttpClient({
  proxy: {
    url: "socks5://gate.proxyhat.com:1080",
    basicAuth: {
      username: "user-country-DE-session-xyz789",
      password: "YOUR_PASSWORD",
    },
  },
});

const res = await fetch("https://httpbin.org/ip", { client: socksClient });
console.log(await res.text());
// Bun — SOCKS5 в строке proxy

const res = await fetch("https://httpbin.org/ip", {
  proxy: "socks5://user-country-DE-session-xyz789:YOUR_PASSWORD@gate.proxyhat.com:1080",
});
console.log(await res.text());

SOCKS5 полезен, когда целевой сайт работает по нестандартным протоколам или когда HTTP-прокси блокируется корпоративным файрволом. Latency через SOCKS5 обычно на 20–50 ms выше из-за дополнительного уровня инкапсуляции.

Переменные окружения HTTP_PROXY / HTTPS_PROXY

Оба рантайма поддерживают переменные окружения HTTP_PROXY и HTTPS_PROXY. Это удобно для быстрого тестирования и для скриптов, где прокси нужен глобально:

# Терминал
export HTTPS_PROXY="http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"

deno run --allow-net proxy_env.ts
# или
bun run proxy_env.ts
// proxy_env.ts — работает в обоих рантаймах

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

Однако env-var подход имеет ограничения: нельзя задать разные прокси для разных запросов в одном процессе, нельзя динамически менять страну или сессию, нельзя комбинировать residential и datacenter в одном скрипте. Для production-скрейпинга используйте per-client конфигурацию через Deno.createHttpClient или опцию proxy в Bun.

Ротация sticky-сессий с Promise.all и AbortController

Резидентные прокси нужны для сайтов с агрессивной антибот-защитой: e-commerce, соцсети, SERP. Datacenter-IP блокируются на этапе TLS-fingerprinting или IP-reputation-проверки. Резидентные адреса выглядят как реальные пользователи и имеют значительно более высокий success rate — по нашим наблюдениям, для защищённых сайтов разница достигает 3–5 раз.

Следующий пример создаёт пул из 10 sticky-сессий, каждая со своим выходным IP, и отправляет запросы параллельно через Promise.all с таймаутом через AbortController:

// bun run rotation_pool.ts

const PROXY_GATE = "gate.proxyhat.com:8080";
const PASSWORD = "YOUR_PASSWORD";
const TARGET = "https://httpbin.org/ip";

function buildProxyUrl(sessionId: string, country = "US"): string {
  const username = `user-country-${country}-session-${sessionId}`;
  return `http://${username}:${PASSWORD}@${PROXY_GATE}`;
}

async function fetchWithTimeout(
  url: string,
  proxy: string,
  timeoutMs = 10000,
): Promise<{ session: string; status: number; body: string }> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const res = await fetch(url, {
      proxy,
      signal: controller.signal,
    });
    const body = await res.text();
    return { session: proxy, status: res.status, body };
  } finally {
    clearTimeout(timer);
  }
}

// Генерируем 10 уникальных сессий
const sessions = Array.from({ length: 10 }, (_, i) => `sess-${Date.now()}-${i}`);

const results = await Promise.all(
  sessions.map((sid) =>
    fetchWithTimeout(TARGET, buildProxyUrl(sid))
      .catch((err) => ({ session: sid, status: 0, body: err.message }))
  ),
);

for (const r of results) {
  console.log(`[${r.status}] ${r.session.slice(0, 40)} → ${r.body.slice(0, 60)}`);
}

Каждая сессия получит свой IP-адрес от ProxyHat. Если один запрос упадёт по таймауту, Promise.all не прервёт остальные, потому что ошибка перехватывается в .catch(). Для Deno версия идентична, но proxy заменяется на client: Deno.createHttpClient({ proxy: { url, basicAuth } }).

Production-паттерны: ретраи, CA-сертификаты, переиспользование соединений

Ретраи с экспоненциальной задержкой

// Работает в Deno и Bun

async function fetchWithRetry(
  url: string,
  proxyFn: (attempt: number) => string,
  maxRetries = 5,
  baseDelay = 1000,
): Promise<Response> {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 15000);

    try {
      const res = await fetch(url, {
        proxy: proxyFn(attempt),
        signal: controller.signal,
      });

      if (res.status === 200) return res;
      if (res.status === 429 || res.status >= 500) {
        // Retryable
        const retryAfter = res.headers.get("retry-after");
        const delay = retryAfter
          ? parseInt(retryAfter, 10) * 1000
          : baseDelay * Math.pow(2, attempt);
        await new Promise((r) => setTimeout(r, delay));
        continue;
      }

      // Non-retryable
      return res;
    } catch (err) {
      lastError = err as Error;
      const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 500;
      await new Promise((r) => setTimeout(r, delay));
    } finally {
      clearTimeout(timer);
    }
  }

  throw lastError ?? new Error("Max retries exceeded");
}

// Ротация прокси на каждой попытке
let counter = 0;
const result = await fetchWithRetry(
  "https://httpbin.org/status/200",
  () => {
    const sid = `retry-${Date.now()}-${counter++}`;
    return `http://user-country-US-session-${sid}:YOUR_PASSWORD@gate.proxyhat.com:8080`;
  },
);
console.log(result.status);

Кастомные CA-сертификаты в Deno

Deno позволяет передавать CA-сертификаты напрямую в createHttpClient, что полезно при работе через MITM-прокси или в корпоративных сетях:

// Deno — кастомный CA

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: "YOUR_PASSWORD" },
  },
  caCerts: [caCert],
});

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

Bun пока не имеет эквивалента caCerts — для корпоративных сред с MITM-прокси Deno предпочтительнее.

Переисользование соединений

В Deno клиент, созданный через Deno.createHttpClient, переиспользует TCP-соединения автоматически. Создавайте один клиент на сессию и переиспользуйте его для всех запросов в рамках этой сессии — это снижает latency на 30–80 ms за счёт устранения TLS-handshake на каждом запросе.

В Bun fetch также поддерживает connection pooling под капотом, но поскольку прокси указывается строкой, пул привязан к идентичной строке прокси. Меняете страну или сессию — получаете новое соединение. Для sticky-сессий это нормально; для агрессивной ротации учитывайте overhead.

ProxyHat Node SDK: работает в обоих рантаймах

ProxyHat предоставляет Node.js SDK, который совместим с Bun (полная совместимость с npm-пакетами) и частично с Deno через npm: спецификатор. SDK абстрагирует форматирование username, ротацию и обработку ошибок:

// Bun — npm install @proxyhat/node-sdk
// Deno — deno run --allow-net --allow-env sdk_demo.ts

import { ProxyHat } from "npm:@proxyhat/node-sdk";
// или в Bun: import { ProxyHat } from "@proxyhat/node-sdk";

const ph = new ProxyHat({
  username: "YOUR_USERNAME",
  password: "YOUR_PASSWORD",
  gateway: "gate.proxyhat.com",
  port: 8080,
});

// Получаем URL прокси с геотаргетингом
const proxyUrl = ph.getProxyUrl({
  country: "DE",
  city: "berlin",
  session: "order-flow-001",
});

console.log(proxyUrl);
// http://user-country-DE-city-berlin-session-order-flow-001:YOUR_PASSWORD@gate.proxyhat.com:8080

// Используем в Bun fetch
const res = await fetch("https://httpbin.org/ip", { proxy: proxyUrl });
console.log(await res.text());

SDK полезен, когда логика ротации сложная: взвешенный выбор страны, ротация по времени, fallback на datacenter при исчерпании residential-лимита. Для простых скриптов достаточно ручного форматирования строки — overhead SDK не оправдан.

Сравнение Deno и Bun для прокси-задач

ВозможностьDenoBun
Указание проксиDeno.createHttpClient({ proxy }) + { client }fetch(url, { proxy })
SOCKS5Через proxy.urlЧерез строку proxy
Кастомные CAcaCerts в клиентеНет прямого API
Env-var проксиПоддерживаетсяПоддерживается
Connection poolingНа уровне клиентаНа уровне fetch
npm-совместимостьЧерез npm: спецификаторНативная

Этика и правовые аспекты

Скрейпинг публичных данных в большинстве юрисдикций легален, но границы размыты. В США Computer Fraud and Abuse Act (CFAA) исторически использовался против скрейпинга, хотя прецедент hiQ Labs v. LinkedIn (9-й округ, 2022) подтвердил, что скрейпинг публичных данных не нарушает CFAA. В ЕС GDPR регулирует обработку персональных данных — IP-адреса считаются персональными данными, и их сбор требует законного основания.

Практические принципы:

  • Сначала проверяйте официальный API — многие платформы предоставляют данные легально и быстрее.
  • Уважайте robots.txt и Terms of Service, даже если они юридически не обязывают.
  • Ограничивайте частоту запросов: 1–2 запроса в секунду на сессию — разумный минимум для большинства сайтов.
  • Не собирайте персональные данные без правового основания.
  • Не обходьте технические меры защиты (CAPTCHA, paywalls) автоматически — это может квалифицироваться как нарушение.

Подробнее о применении прокси для скрейпинга — в нашем материале по использованию прокси для веб-скрейпинга. Для SERP-трекинга см. руководство по отслеживанию поисковой выдачи. Полный список доступных локаций — на странице локаций ProxyHat. Тарифы и лимиты — на странице цен. Документация по API доступна на docs.proxyhat.com.

Ключевые выводы

  • fetch игнорирует прокси по умолчанию — это соответствует стандарту WHATWG, а не баг рантайма.
  • Deno использует Deno.createHttpClient({ proxy }) + { client } в fetch — гибко, поддерживает caCerts.
  • Bun принимает прокси строкой в fetch(url, { proxy }) — лаконично, идеально для быстрых скриптов.
  • Геотаргетинг и sticky-сессии кодируются в username: user-country-US-session-abc123.
  • SOCKS5 доступен на порту 1080 — используйте, когда HTTP-прокси нежелателен.
  • Env-var удобен для тестирования; per-client — для production.
  • Ротация сессий через Promise.all + AbortController даёт параллелизм и устойчивость к таймаутам.
  • Ретраи с backoff обязательны для production — 429 и 5xx требуют задержки, а не повтора в лоб.

Использование прокси в Deno и Bun не сложнее, чем в Node.js, если знать, куда смотреть. Deno даёт больше контроля над TLS и сертификатами; Bun — скорость и простоту. Выбирайте по потребностям проекта, а не по хайпу.

Готовы начать?

Доступ к более чем 50 млн резидентных IP в 148+ странах с AI-фильтрацией.

Смотреть ценыРезидентные прокси
← Вернуться в Блог