Dlaczego natywny fetch() ignoruje proxy w Deno i Bun
Standard fetch() zdefiniowany przez WHATWG Fetch API nie zawiera żadnego mechanizmu konfiguracji proxy. To celowa decyzja projektowa — specyfikacja opisuje wyłącznie warstwę HTTP, a routing sieciowy pozostawia środowisku uruchomieniowemu. Przez to fetch() w przeglądarce, Node.js, Deno i Bun zachowuje się inaczej niż starsze biblioteki takie jak request czy axios, które automatycznie czytały zmienne środowiskowe HTTP_PROXY/HTTPS_PROXY.
Dla programistów JavaScript używających proxy w Deno i Bun oznacza to konieczność jawnego przekazania konfiguracji proxy do klienta HTTP. Każde środowisko rozwiązuje to inaczej: Deno wymaga utworzenia dedykowanego klienta przez Deno.createHttpClient, a Bun rozszerza sygnaturę fetch() o opcję proxy. W tym artykule pokażemy konfigurację krok po kroku, z kodem produkcyjnym i przykładami dla ProxyHat.
Kontekst techniczny: jak Deno i Bun rozwiązują problem proxy
Deno: Deno.createHttpClient({ proxy })
Deno udostępnia API Deno.createHttpClient, które tworzy konfigurowalny klient HTTP. Obiekt klienta przekazuje się do fetch() jako opcję client. Proxy konfiguruje się przez właściwość proxy z polami url i opcjonalnie basicAuth.
// deno run --allow-net --unstable-net proxy_fetch.ts
const proxyUrl = "http://gate.proxyhat.com:8080";
const username = "user-country-US-session-abc123";
const password = "twoje_haslo";
const client = Deno.createHttpClient({
proxy: {
url: proxyUrl,
basicAuth: { username, password },
},
});
try {
const res = await fetch("https://httpbin.org/ip", { client });
const data = await res.json();
console.log("IP przez proxy:", data.origin);
} catch (err) {
console.error("Błąd żądania:", err.message);
} finally {
client.close();
}
Ważne: Deno.createHttpClient wymaga flagi --unstable-net w starszych wersjach Deno (przed 2.2). Od Deno 2.2+ API jest stabilne. Klient należy zamykać przez client.close(), aby uniknąć wycieków zasobów przy dużej liczbie żądań.
Bun: fetch(url, { proxy })
Bun przyjmuje bardziej bezpośrednie podejście — opcja proxy jest wbudowana w standardową sygnaturę fetch(). Nie trzeba tworzyć osobnego obiektu klienta.
// bun run proxy_fetch.ts
const proxyUrl = "http://user-country-US-session-abc123:twoje_haslo@gate.proxyhat.com:8080";
try {
const res = await fetch("https://httpbin.org/ip", {
proxy: proxyUrl,
});
const data = await res.json();
console.log("IP przez proxy:", data.origin);
} catch (err) {
console.error("Błąd żądania:", err.message);
}
Bun parsuje URL proxy zgodnie z RFC 7617 (HTTP Basic Authentication), więc poświadczenia mogą być osadzone bezpośrednio w adresie. To najkrótsza droga do działania proxy w JavaScript — jedna linijka.
Kodowanie geo-targetingu i sesji sticky w nazwie użytkownika
ProxyHat, podobnie jak większość zaawansowanych dostawców proxy, pozwala na przekazywanie flag geo i sesji w nazwie użytkownika. To eliminuje potrzebę osobnych endpointów dla każdego kraju czy miasta.
| Flaga | Przykład nazwy użytkownika | Opis |
|---|---|---|
| Kraj | user-country-DE | IP z Niemiec |
| Kraj + miasto | user-country-DE-city-berlin | IP z Berlina |
| Sticky session | user-session-abc123 | Ten sam IP dla wielu żądań |
| Kraj + sesja | user-country-US-session-abc123 | Stały IP z USA |
Przykład w Deno z geo-targetingiem i sticky session:
// Geo-targeting + sticky session w Deno
function createProxyClient(country: string, session: string) {
const username = `user-country-${country}-session-${session}`;
return Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username, password: "twoje_haslo" },
},
});
}
const deClient = createProxyClient("DE", "sess-001");
const usClient = createProxyClient("US", "sess-002");
const [deRes, usRes] = await Promise.all([
fetch("https://httpbin.org/ip", { client: deClient }),
fetch("https://httpbin.org/ip", { client: usClient }),
]);
console.log("DE IP:", (await deRes.json()).origin);
console.log("US IP:", (await usRes.json()).origin);
deClient.close();
usClient.close();
SOCKS5 na porcie 1080
Gdy HTTP CONNECT nie wystarcza — np. w środowiskach z restrykcyjnymi firewallami lub gdy potrzebujesz pełnego tunelowania TCP — ProxyHat udostępnia SOCKS5 na porcie 1080.
// Bun — SOCKS5 proxy
const socksUrl = "socks5://user-country-DE-session-abc123:twoje_haslo@gate.proxyhat.com:1080";
const res = await fetch("https://httpbin.org/ip", {
proxy: socksUrl,
});
console.log(await res.json());
W Deno obsługa SOCKS5 przez Deno.createHttpClient zależy od wersji — w starszych kompilacjach konieczne może być użycie zewnętrznej biblioteki takiej jak socks z npm. Bun natywnie obsługuje SOCKS5 w opcji proxy od wersji 1.1+.
Zmienne środowiskowe HTTP_PROXY/HTTPS_PROXY
Zarówno Deno, jak i Bun automatycznie czytają zmienne środowiskowe HTTP_PROXY i HTTPS_PROXY (zgodnie z konwencją z dokumentacją MDN o nagłówkach HTTP). To wygodne w środowiskach CI/CD i kontenerach Docker.
# Ustawienie zmiennej środowiskowej
export HTTP_PROXY="http://user-country-US:twoje_haslo@gate.proxyhat.com:8080"
export HTTPS_PROXY="http://user-country-US:twoje_haslo@gate.proxyhat.com:8080"
# Deno automatycznie użyje proxy
deno run --allow-net --allow-env script.ts
# Bun również automatycznie użyje proxy
bun run script.ts
Kiedy używać zmiennych środowiskowych vs konfiguracji per-klient?
- Zmienne środowiskowe — gdy wszystkie żądania mają przechodzić przez ten sam proxy (np. całe środowisko w firmowym VPN lub kontenerze).
- Konfiguracja per-klient — gdy potrzebujesz różnych proxy dla różnych żądań, np. rotacja krajów, osobne sesje sticky, lub mieszanie proxy residential z datacenter.
Proxy residential dla celów z wysokim ryzykiem blokady
Serwery datacenter są łatwo wykrywane przez zaawansowane systemy anti-bot (Cloudflare, Akamai, Datadome). Adresy IP z bloków AWS, DigitalOcean czy Hetzner są oznaczane jako hosting w bazach takich jak MaxMind, co często skutkuje CAPTCHA lub HTTP 403. Proxy residential używa adresów przydzielonych prawdziwym dostawcom ISP, co sprawia, że ruch wygląda jak organiczny.
Poniżej przykład rotacji puli sesji sticky z wykorzystaniem Promise.all i AbortController jako timeout — w Bun, ale wzorzec działa identycznie w Deno:
// Bun — rotacja sesji sticky z timeoutem i obsługą błędów
const PROXY_GATEWAY = "gate.proxyhat.com:8080";
const PASSWORD = "twoje_haslo";
const TARGET_URL = "https://api.example.com/data";
const TIMEOUT_MS = 10_000;
interface ScrapeResult {
session: string;
status: number;
body: unknown;
}
async function fetchWithSession(sessionId: string): Promise<ScrapeResult> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
const proxyUrl = `http://user-country-US-session-${sessionId}:${PASSWORD}@${PROXY_GATEWAY}`;
try {
const res = await fetch(TARGET_URL, {
proxy: proxyUrl,
signal: controller.signal,
headers: {
"Accept": "application/json",
"User-Agent": "MyApp/1.0",
},
});
return {
session: sessionId,
status: res.status,
body: await res.json(),
};
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") {
console.warn(`Sesja ${sessionId}: timeout po ${TIMEOUT_MS}ms`);
} else {
console.error(`Sesja ${sessionId}:`, err.message);
}
return { session: sessionId, status: 0, body: null };
} finally {
clearTimeout(timeout);
}
}
// Uruchom 10 równoległych żądań z różnymi sesjami sticky
const sessions = Array.from({ length: 10 }, (_, i) => `sess-${i.toString().padStart(3, "0")}`);
const results = await Promise.all(sessions.map(fetchWithSession));
const successCount = results.filter((r) => r.status === 200).length;
console.log(`Sukces: ${successCount}/${results.length} (${(successCount / results.length * 100).toFixed(1)}%)`);
Ten wzorzec daje pełną kontrolę nad współbieżnością, timeoutami i identyfikacją sesji. Przy 10 równoległych żądaniach z timeoutem 10 s, całkowity czas wykonania rzadko przekracza 12 s nawet przy jednym retried request.
Wskazówki produkcyjne: retry, backoff, własne CA i ponowne użycie połączeń
Retry z wykładniczym backoffem
// Deno — retry z backoffem i ponownym użyciem klienta
const MAX_RETRIES = 3;
const BASE_DELAY_MS = 500;
async function fetchWithRetry(
url: string,
client: Deno.HttpClient,
retries = MAX_RETRIES,
): Promise<Response> {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const res = await fetch(url, { client });
if (res.status === 429 || res.status >= 500) {
throw new Error(`HTTP ${res.status}`);
}
return res;
} catch (err) {
if (attempt === retries) throw err;
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
console.warn(`Próba ${attempt + 1}/${retries + 1} nieudana, retry za ${delay}ms`);
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error("Nieosiągalne");
}
// Użycie — klient jest tworzony raz i ponownie używany
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: "user-country-PL", password: "twoje_haslo" },
},
});
try {
const res = await fetchWithRetry("https://api.example.com/data", client);
console.log(await res.json());
} finally {
client.close();
}
Własne CA w Deno (caCerts)
Jeśli Twoje proxy używa certyfikatu TLS z wewnętrznego CA (np. w środowisku korporacyjnym), Deno pozwala na dostarczenie własnych certyfikatów przez opcję caCerts:
// Deno — własne CA
const caCert = await Deno.readTextFile("./internal-ca.pem");
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: "user-country-PL", password: "twoje_haslo" },
},
caCerts: [caCert],
});
const res = await fetch("https://internal.example.com/api", { client });
console.log(res.status);
client.close();
Porównanie: natywne API vs ProxyHat SDK
ProxyHat udostępnia SDK dla Node.js, które działa również pod Deno i Bun dzięki kompatybilności npm. Poniżej porównanie podejścia natywnego i SDK:
| Cecha | Natywne API (Deno/Bun) | ProxyHat SDK |
|---|---|---|
| Konfiguracja proxy | Ręczne budowanie URL/obiektu | Automatyczne z konfiguracją |
| Rotacja IP | Własna logika sesji | Wbudowana rotacja |
| Retry/backoff | Ręczna implementacja | Wbudowane z konfiguracją |
| Geo-targeting | Przez nazwę użytkownika | Przez API SDK |
| Zależności | Brak (natywne API) | Jeden pakiet npm |
| Wydajność | Maksymalna (zero narzutów) | Niewielki narzut |
// ProxyHat SDK — działa w Deno i Bun przez npm
// npm install @proxyhat/sdk
import { ProxyHat } from "@proxyhat/sdk";
const ph = new ProxyHat({
username: "user",
password: "twoje_haslo",
gateway: "gate.proxyhat.com",
port: 8080,
defaultCountry: "US",
});
// Automatyczna rotacja i retry
const result = await ph.fetch("https://api.example.com/data", {
country: "DE",
session: "my-session",
retries: 3,
timeout: 10_000,
});
console.log(await result.json());
SDK upraszcza konfigurację, ale natywne API daje pełną kontrolę i zero dodatkowych zależności — co jest szczególnie ważne w środowiskach serverless i edge, gdzie rozmiar bundla ma znaczenie.
Etyczny scraping: publiczne dane, CFAA i GDPR
Scraping danych publicznych jest legalny w wielu jurysdykcjach, ale granice zależą od kontekstu. W USA Computer Fraud and Abuse Act (CFAA) był podstawą procesów przeciwko scraperom, choć orzeczenie Van Buren v. United States (2021) zawęziło definicję „nieautoryzowanego dostępu”. W UE GDPR ogranicza zbieranie danych osobowych, a wyrok Trybunału Sprawiedliwości UE w sprawie C-302/20 (hiQ vs LinkedIn) potwierdził prawo do scrapowania publicznie dostępnych danych.
Najlepsze praktyki:
- Zawsze sprawdzaj
robots.txti warunki usługi (ToS) przed rozpoczęciem scrapowania. - Używaj oficjalnego API, jeśli jest dostępne — jest szybsze, stabilniejsze i legalne.
- Limituj częstotliwość żądań — 1–5 żądań/s na domenę to bezpieczny próg dla większości stron.
- Nie zbieraj danych osobowych bez podstawy prawnej zgodnej z GDPR.
- Identyfikuj swojego bota w nagłówku
User-Agentz danymi kontaktowymi.
Sprawdź nasze przypadki użycia web scrapingu oraz śledzenie SERP, aby zobaczyć, jak proxy residential zwiększa niezawodność zbierania danych. Pełną listę dostępnych lokalizacji znajdziesz na stronie lokalizacje proxy, a ceny i plany na stronie cennika ProxyHat.
Najważniejsze wnioski
fetch()w Deno i Bun nie czyta proxy domyślnie — trzeba przekazać konfigurację jawnie.- Deno używa
Deno.createHttpClient({ proxy })+fetch(url, { client }).- Bun używa
fetch(url, { proxy: 'http://...' })w jednej linii.- Geo-targeting i sesje sticky koduje się w nazwie użytkownika:
user-country-US-session-abc123.- SOCKS5 jest dostępny na porcie 1080 dla pełnego tunelowania TCP.
- Proxy residential jest niezbędny dla celów z systemami anti-bot (Cloudflare, Datadome).
- Produkcyjne wzorce: retry z backoffem,
AbortControllertimeout, ponowne użycie klienta.- Zmienne
HTTP_PROXY/HTTPS_PROXYsą czytane automatycznie przez oba środowiska.
FAQ
Czym jest używanie proxy w Deno i Bun?
Używanie proxy w Deno i Bun polega na jawnym przekazaniu konfiguracji proxy do klienta HTTP, ponieważ standardowy fetch() nie obsługuje proxy domyślnie. W Deno tworzy się klienta przez Deno.createHttpClient({ proxy }), a w Bun przekazuje się opcję proxy bezpośrednio do fetch(). Proxy pozwala na ukrycie IP, geo-targeting i omijanie blokad anti-bot.
Dlaczego używanie proxy w Deno i Bun jest ważne dla użytkowników proxy?
Te nowoczesne środowiska JavaScript nie obsługują proxy automatycznie, co oznacza, że programiści muszą wiedzieć, jak je skonfigurować. Bez poprawnej konfiguracji żądania wychodzą z IP serwera, co prowadzi do blokad, CAPTCHA i banów IP. Prawidłowa konfiguracja proxy residential w Deno i Bun pozwala na stabilny scraping, monitorowanie cen i zbieranie danych SERP.
Który typ proxy działa najlepiej w Deno i Bun?
Proxy residential jest najlepszy dla celów z systemami anti-bot, ponieważ używa IP od prawdziwych dostawców ISP. Proxy datacenter jest tańszy i szybszy, ale łatwo wykrywany. Proxy mobile jest najtrudniejszy do zablokowania, ale najdroższy. Dla większości zastosowań w Deno i Bun — scraping, SERP tracking, e-commerce — residential na porcie 8080 oferuje najlepszy stosunek ceny do niezawodności.
Jak unikać blokad używając proxy w Deno i Bun?
Używaj sesji sticky (user-session-abc123) dla żądań wymagających ciągłości IP, rotuj sesje dla równoległych żądań, ustawiaj realistyczny User-Agent, limituj częstotliwość do 1–5 żądań/s na domenę, implementuj retry z wykładniczym backoffem i używaj AbortController z timeoutem 10 s. Zawsze sprawdzaj robots.txt i warunki usługi przed scrapowaniem.






