Deno와 Bun에서 프록시 사용하기: 실전 가이드

Deno와 Bun의 최신 JavaScript 런타임에서 fetch()에 프록시를 적용하는 방법을 코드 중심으로 설명합니다. Deno.createHttpClient, Bun fetch proxy 옵션, 스티키 세션, SOCKS5, 재시도 패턴까지 다룹니다.

Using Proxies in Deno and Bun: A Code-First Guide
이 글의 목차

Deno와 Bun에서 프록시 사용하기: 왜 fetch()가 프록시를 무시하는가

Node.js, Deno, Bun 어느 런타임을 사용하든 기본 fetch()HTTP_PROXY 환경 변수를 자동으로 읽지 않거나, 읽더라도 인증이 필요한 프록시에서 실패합니다. WHATWG Fetch 표준 자체가 프록시 설정을 명세에 포함하지 않기 때문입니다. Deno와 Bun에서 프록시를 사용하려면 런타임별 API를 명시적으로 호출해야 합니다. 이 글에서는 Deno proxy 설정(Deno.createHttpClient), Bun fetch proxy 옵션, ProxyHat 게이트웨이(gate.proxyhat.com:8080) 연동 예제를 코드 중심으로 살펴봅니다.

주요 대상 독자는 JavaScript/TypeScript 개발자로, SERP 추적, 전자상거래 가격 모니터링, 대규모 웹 스크래핑을 현대 런타임에서 처리하려는 분들입니다. 웹 스크래핑SERP 추적 시나리오에서 즉시 적용할 수 있는 예제를 제공합니다.

기술적 배경: 각 런타임이 프록시를 처리하는 방식

Fetch 표준은 네트워크 전송 계층을 추상화하지만, 프록시 URL이나 인증 헤더를 전달하는 방법을 정의하지 않습니다. 따라서 각 런타임이 자체 확장을 제공합니다.

런타임프록시 설정 APIHTTP 인증SOCKS5커스텀 CA
DenoDeno.createHttpClient({ proxy })fetch(url, { client })proxy.basicAuth 또는 URL 내 user:pass@지원 (proxy URL 스킴 socks5://)caCerts 배열
Bunfetch(url, { proxy }) 단일 옵션URL 내 user:pass@지원 (socks5:// 스킴)환경 변수 NODE_EXTRA_CA_CERTS
Node.js (참고)undici ProxyAgent 또는 https-proxy-agentURL 내 자격증명별도 에이전트 필요https.Agent

두 런타임 모두 Deno 공식 문서Bun 공식 문서에서 확인할 수 있듯, proxy fetch JavaScript 코드를 작성할 때 클라이언트 객체를 재사용하면 연결 풀이 유지되어 성능이 향상됩니다.

Deno에서 Deno.createHttpClient로 프록시 설정하기

Deno는 Deno.createHttpClient로 커스텀 HTTP 클라이언트를 만들고, 이를 fetchclient 옵션에 전달합니다. 인증은 proxy.basicAuth 객체 또는 URL에 직접 포함할 수 있습니다.

// deno run --allow-net app.ts
// ProxyHat HTTP 게이트웨이 예제
const client = Deno.createHttpClient({
  proxy: {
    url: "http://gate.proxyhat.com:8080",
    basicAuth: {
      username: "user-country-US",
      password: "pass",
    },
  },
});

try {
  const res = await fetch("https://httpbin.org/ip", { client });
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}`);
  }
  const data = await res.json();
  console.log("IP:", data.origin);
} catch (err) {
  console.error("요청 실패:", err);
} finally {
  client.close();
}

URL에 자격증명을 포함하는 방식도 동작합니다. 다만, basicAuth 객체를 사용하면 사용자명에 콜론이 포함된 경우 인코딩 문제를 피할 수 있습니다.

// URL 내 자격증명 + 스티키 세션
const stickyClient = Deno.createHttpClient({
  proxy: {
    url: "http://user-country-DE-session-abc123:pass@gate.proxyhat.com:8080",
  },
});

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

SOCKS5 사용 (포트 1080)

ProxyHat SOCKS5 게이트웨이는 포트 1080에서 동작합니다. Deno는 socks5:// 스킴을 지원합니다.

// SOCKS5 예제
const socksClient = Deno.createHttpClient({
  proxy: {
    url: "socks5://user-country-JP-session-sess01:pass@gate.proxyhat.com:1080",
  },
});

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

Bun에서 fetch proxy 옵션 사용하기

Bun은 더 간결합니다. fetch 호출에 proxy 옵션을 직접 전달합니다.

// bun run app.ts
// Bun fetch proxy 단일 옵션
const res = await fetch("https://httpbin.org/ip", {
  proxy: "http://user-country-US:pass@gate.proxyhat.com:8080",
});

if (!res.ok) {
  throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
console.log("IP:", data.origin);

지리 타겟팅과 스티키 세션은 사용자명에 인코딩합니다. 형식은 user-country-XX-city-yy-session-ZZZ입니다. 자세한 위치 목록은 프록시 위치 페이지를 참고하세요.

// Bun: 도시 단위 지리 타겟팅 + 스티키 세션
const proxyUrl = "http://user-country-DE-city-berlin-session-order42:pass@gate.proxyhat.com:8080";

const res = await fetch("https://api.example.com/data", {
  proxy: proxyUrl,
  headers: { "User-Agent": "MyBot/1.0" },
});
console.log(res.status, await res.text());

환경 변수 경로와 언제 per-client 설정을 선호해야 하는가

Deno와 Bun 모두 HTTP_PROXY/HTTPS_PROXY 환경 변수를 일부 버전에서 인식합니다. 하지만 혼합 워크로드에서는 권장하지 않습니다.

  • 환경 변수: CLI 도구, 간단한 스크립트, 전체 프로세스가 단일 프록시를 사용할 때 적합.
  • per-client 설정: 요청마다 다른 국가/세션을 사용하거나, 프록시와 직접 연결을 혼합할 때 필수.
// 환경 변수 (Deno)
// HTTP_PROXY=http://user-country-US:pass@gate.proxyhat.com:8080 deno run --allow-net --allow-env app.ts

// per-client가 더 유연한 경우: 국가별 동시 요청
const countries = ["US", "DE", "JP", "BR", "IN"];
const results = await Promise.all(
  countries.map(async (cc) => {
    const c = Deno.createHttpClient({
      proxy: {
        url: `http://user-country-${cc}:pass@gate.proxyhat.com:8080`,
      },
    });
    try {
      const r = await fetch("https://httpbin.org/ip", { client: c });
      return { cc, data: await r.json() };
    } finally {
      c.close();
    }
  })
);
console.log(results);

실전 예제: 스티키 세션 풀로 동시 요청 회전하기

차단이 잦은 타겟에서는 각 세션이 일관된 IP를 유지하면서도, 요청 간에 세션을 분산하는 것이 효과적입니다. Promise.allAbortController 타임아웃을 조합한 Deno 예제입니다.

// Deno: 스티키 세션 풀 + 타임아웃 + 재시도
const SESSIONS = 10;
const TIMEOUT_MS = 8000;
const MAX_RETRIES = 3;

function makeClient(sessionId: string, country = "US") {
  return Deno.createHttpClient({
    proxy: {
      url: `http://user-country-${country}-session-${sessionId}:pass@gate.proxyhat.com:8080`,
    },
  });
}

async function fetchWithRetry(url: string, sessionId: string) {
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
    const client = makeClient(sessionId);
    try {
      const res = await fetch(url, { client, signal: controller.signal });
      clearTimeout(timer);
      if (res.status === 429 || res.status >= 500) {
        client.close();
        await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
        continue;
      }
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const body = await res.text();
      client.close();
      return body;
    } catch (err) {
      clearTimeout(timer);
      client.close();
      if (attempt === MAX_RETRIES) throw err;
      await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
    }
  }
  throw new Error("최대 재시도 도달");
}

// 10개 세션 동시 실행
const targets = Array.from({ length: SESSIONS }, (_, i) => `https://httpbin.org/delay/1?t=${i}`);
const sessionIds = Array.from({ length: SESSIONS }, (_, i) => `sess-${i}`);

const results = await Promise.allSettled(
  targets.map((url, i) => fetchWithRetry(url, sessionIds[i]))
);

for (const [i, r] of results.entries()) {
  if (r.status === "fulfilled") {
    console.log(`[${sessionIds[i]}] OK (${r.value.length} bytes)`);
  } else {
    console.error(`[${sessionIds[i]}] FAIL`, r.reason);
  }
}

Bun에서도 동일한 패턴을 사용할 수 있으며, proxy 옵션만 교체하면 됩니다. Bun fetch proxy는 클라이언트 객체 없이 직접 전달하므로 코드가 더 짧아집니다.

// Bun: 동시 스티키 세션
const N = 5;
const results = await Promise.allSettled(
  Array.from({ length: N }, async (_, i) => {
    const proxy = `http://user-country-US-session-s${i}:pass@gate.proxyhat.com:8080`;
    const ctrl = new AbortController();
    const timer = setTimeout(() => ctrl.abort(), 8000);
    try {
      const res = await fetch(`https://httpbin.org/ip?i=${i}`, {
        proxy,
        signal: ctrl.signal,
      });
      return await res.json();
    } finally {
      clearTimeout(timer);
    }
  })
);
console.log(results.map((r) => (r.status === "fulfilled" ? r.value : r.reason)));

프로덕션 팁: 재시도, 커스텀 CA, 연결 재사용

지수 백오프 재시도

429와 5xx 응답에서만 재시도하고, Retry-After 헤더가 있으면 준수합니다. 위 예제의 2 ** attempt * 500 패턴은 500ms, 1s, 2s 간격을 만들어냅니다.

Deno에서 커스텀 CA 인증서

기업 환경에서 MITM 프록시를 거칠 때 caCerts 옵션으로 PEM 인증서를 추가할 수 있습니다.

// Deno: 커스텀 CA + 프록시
const caPem = await Deno.readTextFile("./corporate-ca.pem");

const client = Deno.createHttpClient({
  proxy: {
    url: "http://user-country-US:pass@gate.proxyhat.com:8080",
  },
  caCerts: [caPem],
});

const res = await fetch("https://internal.example.com/health", { client });
console.log(res.status);
client.close();

연결 재사용으로 성능 향상

동일한 세션/국가 조합을 반복 사용할 때는 Deno.createHttpClient 객체를 한 번 만들고 재사용하세요. 연결 풀(HTTP/2 멀티플렉싱)이 유지되어 평균 지연이 200ms 이상 감소할 수 있습니다. Bun은 fetch 호출마다 새 연결을 맺을 수 있으므로, 대량 요청에서는 Deno의 클라이언트 재사용이 유리할 수 있습니다.

ProxyHat Node SDK 병행 사용

ProxyHat Node SDK는 Deno와 Bun 모두에서 동작합니다. 런타임별 API 차이를 추상화하고 싶을 때 유용합니다.

// ProxyHat Node SDK (Deno/Bun 호환)
// deno npm:proxyhat-sdk 또는 bun add proxyhat-sdk
import { ProxyHat } from "proxyhat-sdk";

const ph = new ProxyHat({
  username: "user-country-US",
  password: "pass",
  // 기본 게이트웨이: gate.proxyhat.com:8080
});

const res = await ph.fetch("https://httpbin.org/ip", {
  country: "DE",
  session: "abc123",
});
console.log(await res.json());

SDK를 사용하면 Deno.createHttpClientfetch proxy 옵션을 직접 다루지 않아도 됩니다. 다만, 세밀한 제어(커스텀 CA, 타임아웃, 재시도 로직)가 필요하면 위의 원시 예제를 선호합니다. ProxyHat 문서에서 최신 SDK API를 확인하세요.

공통 실수와 엣지 케이스

  • 사용자명 인코딩 누락: user-country-US에서 콜론이나 공백이 포함되면 URL 인코딩이 필요합니다. basicAuth 객체를 사용하면 안전합니다.
  • 세션 ID 재사용: 동일한 세션 ID를 너무 오래 유지하면 IP가 차단될 수 있습니다. 10~30분 단위로 세션을 교체하세요.
  • 환경 변수와 per-client 충돌: HTTP_PROXY가 설정된 상태에서 per-client 프록시를 지정하면, 런타임에 따라 우선순위가 다를 수 있습니다. 혼합 사용을 피하세요.
  • SOCKS5 포트 혼동: HTTP는 8080, SOCKS5는 1080입니다. 스킴과 포트가 일치해야 합니다.
  • 리소스 누수: Deno에서 Deno.createHttpClient 객체를 close()하지 않으면 파일 디스크립터가 누적됩니다. finally 블록에서 닫으세요.

왜 주거용 프록시가 필요한가

데이터센터 IP는 클라우드 제공자 ASN 블록에 속해 있어, Cloudflare, Akamai, PerimeterX 같은 봇 방지 시스템이 쉽게 식별합니다. 주거용 프록시는 실제 ISP에서 할당된 IP를 사용하므로 차단 확률이 크게 낮아집니다. SERP 추적, 소셜 미디어 연구, 티켓팅 모니터링 등에서 주거용 프록시가 표준입니다.

ProxyHat은 프록시 가격 페이지에서 주거용, 모바일, 데이터센터 프록시를 비교할 수 있으며, 99.9% 업타임과 100개 이상 동시 세션을 지원합니다.

윤리적 스크래핑: 공개 데이터만, 공식 API 우선

프록시는 도구일 뿐, 합법성은 사용 방식에 달려 있습니다. 미국에서는 CFAA(Computer Fraud and Abuse Act)가 무단 접근을 제한하며, EU에서는 GDPR이 개인정보 수집에 동의를 요구합니다. 다음 원칙을 권장합니다.

  • 공식 API가 있으면 먼저 사용하세요. 속도, 안정성, 합법성 면에서 유리합니다.
  • robots.txt를 준수하고, 서비스 약관(ToS)을 검토하세요.
  • 공개 데이터만 수집하고, 로그인이 필요한 페이지는 피하세요.
  • 요청 속도를 합리적으로 유지하세요(예: 도메인당 초당 1~2 요청).
  • 개인정보를 수집하지 않거나, 수집 시 익명화하세요.

핵심 요약

Key Takeaways

  • Deno는 Deno.createHttpClient({ proxy })fetch(url, { client }) 패턴을 사용한다.
  • Bun은 fetch(url, { proxy }) 한 줄로 끝난다.
  • 지리 타겟팅과 스티키 세션은 사용자명에 인코딩한다(user-country-US-session-abc123).
  • HTTP는 포트 8080, SOCKS5는 포트 1080을 사용한다.
  • 주거용 프록시는 차단이 잦은 타겟에서 필수적이다.
  • Promise.all + AbortController + 지수 백오프로 프로덕션 신뢰성을 확보한다.
  • 공식 API를 우선하고, 공개 데이터만 윤리적으로 수집한다.

ProxyHat 게이트웨이(gate.proxyhat.com)는 Deno와 Bun 모두에서 즉시 사용할 수 있습니다. 요금제를 확인하고 무료 평가판으로 시작하세요.

자주 묻는 질문

Deno와 Bun에서 프록시를 사용하는 방법은 무엇인가요?

Deno는 Deno.createHttpClient({ proxy: { url, basicAuth } })로 클라이언트를 만든 뒤 fetch(url, { client })로 전달합니다. Bun은 fetch(url, { proxy: 'http://user:pass@gate.proxyhat.com:8080' }) 한 줄로 프록시를 지정합니다. 두 런타임 모두 사용자명에 지리 타겟팅과 세션 ID를 인코딩할 수 있으며, SOCKS5는 포트 1080을 사용합니다.

Deno와 Bun에서 프록시 사용이 프록시 사용자에게 중요한 이유는?

WHATWG Fetch 표준이 프록시 설정을 명세에 포함하지 않기 때문에, 기본 fetch()는 HTTP_PROXY 환경 변수를 무시하거나 인증이 필요한 프록시에서 실패합니다. Deno와 Bun의 런타임별 확장 API를 알아야만 인증 프록시, 스티키 세션, 지리 타겟팅을 정확히 제어할 수 있습니다.

Deno와 Bun에서 어떤 프록시 유형이 가장 적합한가요?

차단이 잦은 타겟(SERP, 전자상거래, 소셜 미디어)에서는 주거용 프록시가 가장 적합합니다. 실제 ISP IP를 사용하여 봇 방지 시스템의 식별을 회피할 수 있습니다. 데이터센터 프록시는 속도가 중요하고 차단 위험이 낮은 내부 API 테스트에 적합하며, 모바일 프록시는 가장 높은 신뢰도가 필요한 경우에 사용합니다.

Deno와 Bun에서 프록시 사용 시 차단을 피하려면 어떻게 하나요?

스티키 세션을 사용해 각 세션이 일관된 IP를 유지하게 하되, 10~30분 단위로 세션을 교체하세요. 지수 백오프 재시도로 429와 5xx 응답을 처리하고, AbortController로 타임아웃을 설정하세요. 요청 속도를 합리적으로 유지하고, User-Agent와 헤더를 자연스럽게 구성하며, 공개 데이터만 수집하는 것이 중요합니다.

ProxyHat SDK가 Deno와 Bun에서 동작하나요?

네, ProxyHat Node SDK는 Deno와 Bun 모두에서 동작합니다. 런타임별 API 차이를 추상화하여 country와 session 옵션만으로 요청을 보낼 수 있습니다. 다만 커스텀 CA 인증서, 세밀한 타임아웃 제어, 복잡한 재시도 로직이 필요하면 Deno.createHttpClient나 Bun fetch proxy 옵션을 직접 사용하는 것이 더 유연합니다.

시작할 준비가 되셨나요?

148개국 이상의 주거용, ISP, 모바일 프록시. 무료 계정을 만드세요.

무료 계정 만들기
← 블로그로 돌아가기