cf-fetch-socks
v0.2.2
Published
SOCKS5 proxy fetch for Cloudflare Workers — with TLS, pooling, redirects, and decompression
Maintainers
Readme
cf-fetch-socks
A proof of concept SOCKS5 proxy fetch for Cloudflare Workers. Make HTTP/HTTPS requests from Workers through a SOCKS5 proxy with TLS, connection pooling, redirect following, auth support, and content decompression baked in.
Install
bun add cf-fetch-socksnpm install cf-fetch-socksQuick start
const response = await socksFetch('https://api.example.com/data', {
proxy: 'socks5://user:[email protected]:1080',
});to reuse the proxy connections to the same host you can use this format:
import { socksFetch, Proxy, socks5Tunnel } from 'cf-fetch-socks';
const proxy = new Proxy(socks5Tunnel, {
hostname: 'my-proxy.example.com',
port: 1080,
username: 'user',
password: 'pass',
});
const response = await socksFetch('https://api.example.com/data', { proxy });
const data = await response.json();Features
- Full SOCKS5 protocol (RFC 1928) with username/password auth (RFC 1929)
- TLS 1.3: over the tunnel via
@reclaimprotocol/tls - Connection pooling: reuse TCP/TLS connections to the same target host
- TLS session resumption: PSK caching for faster reconnects
- Automatic redirect following: with correct method preservation (301/302/303 → POST→GET, 307/308 → preserve method + body)
- Content decompression: gzip, brotli, deflate (via
node:zlib)
How it works
socksFetch(url, { proxy })
→ Proxy.connect(target) # Open or reuse a connection
→ SOCKS5 tunnel to proxy # TCP connect → greeting → auth → connect
→ TLS 1.3 wrap (if HTTPS) # Pure-JS TLS over the tunnel
→ executeRedirectLoop(proxy, request) # Follow redirects
→ HTTP/1.1 request over the tunnel
→ Parse response headers
→ If redirect: drain, rebuild, loop
→ If final: decompress, decode, return ResponseAPI
socksFetch(url, options)
function socksFetch(url: string | URL | Request, options: ProxyFetchOptions): Promise<Response>;The main entry point. Makes an HTTP/HTTPS request through a SOCKS5 proxy and returns a standard Response.
ProxyFetchOptions
| Option | Type | Default | Description |
| ------- | ------------------------- | ------------ | ------------------------------------------------------------------------------------- |
| proxy | string \| Proxy | required | SOCKS5 URI or Proxy instance |
| debug | boolean \| DebugOptions | false | Enable debug logging / timing |
| ... | RequestInit | — | All standard fetch options: method, headers, body, signal, redirect, etc. |
Passing proxy as a string (socks5://user:pass@host:1080) creates a non-pooled cached Proxy via Proxy.obtainProxy(). Pass a Proxy instance directly for full control over pooling, timeouts, and TLS session caching.
Proxy
class Proxy {
constructor(
tunnelFn: TunnelFn,
opts: ProxyOptions,
pooled?: boolean, // default: true
);
// Static factories
static obtainProxy(uri: string): Proxy; // Non-pooled, cached
static acquireProxy(uri: string): Proxy; // Pooled, cached
static clearCache(): void;
// Properties
readonly uri: URL;
readonly isPooled: boolean;
readonly idleCount: number;
// Methods
connect(target: ProxyTarget, signal?: AbortSignal): Promise<ProxyConnection>;
release(conn: ProxyConnection): void;
close(): void;
}The Proxy class manages the connection to your SOCKS5 server. When pooled is true, TCP/TLS connections to the same target host are reused across requests.
ProxyOptions
| Field | Type | Required | Description |
| ---------- | -------- | -------- | ------------------------------------------- |
| hostname | string | ✓ | SOCKS5 proxy hostname |
| port | number | ✓ | SOCKS5 proxy port (typically 1080) |
| username | string | — | Proxy auth username |
| password | string | — | Proxy auth password |
| timeout | number | — | Connection timeout in ms (default: 10000) |
Advanced usage
Pooled proxy
const proxy = Proxy.acquireProxy('socks5://user:[email protected]:1080');
const r1 = await socksFetch('https://api.example.com/a', { proxy });
// Connection is returned to the pool
const r2 = await socksFetch('https://api.example.com/b', { proxy });
// Reuses the same TCP/TLS connectionManual connection management
import { socks5Tunnel, Proxy } from 'cf-fetch-socks';
const proxy = new Proxy(socks5Tunnel, {
hostname: 'my-proxy.example.com',
port: 1080,
username: 'user',
password: 'pass',
timeout: 5000,
});
const conn = await proxy.connect({ host: 'api.example.com', port: 443, tls: true });
try {
conn.write(/* raw HTTP request bytes */);
// ... read from conn.readable
} finally {
proxy.release(conn);
}Debug / timing
const response = await socksFetch('https://api.example.com/data', {
proxy: 'socks5://user:[email protected]:1080',
debug: true, // simple: logs to console
});
// Or with custom callbacks:
const response = await socksFetch('https://api.example.com/data', {
proxy: 'socks5://user:[email protected]:1080',
debug: {
enable: true,
logFn: (msg) => console.log(msg),
onLine: (line) => myLogger.debug(line),
onDebugEnd: (entries) => {
for (const { label, duration } of entries) {
console.log(`${label}: ${duration.toFixed(1)}ms`);
}
},
},
});Error handling
import {
Socks5AuthError,
ConnectionRefusedError,
ConnectionTimeoutError,
TlsUpgradeError,
AbortError,
ProxyAuthError,
BadGatewayError,
} from 'cf-fetch-socks';
try {
const response = await socksFetch('https://api.example.com/data', { proxy });
} catch (err) {
if (err instanceof Socks5AuthError) {
// Wrong proxy credentials
} else if (err instanceof ConnectionRefusedError) {
// Proxy or target refused the connection
} else if (err instanceof TlsUpgradeError) {
// TLS handshake failed over the tunnel
} else if (err instanceof ProxyAuthError) {
// Proxy returned 407
} else if (err instanceof BadGatewayError) {
// Proxy returned 502
}
}Integration test setup
For integration tests two local docker containers must be running
docker run -p 8080:80 kennethreitz/httpbin
docker run -p 1080:1080 vimagick/microsocks -u username -P passwordand these variables should be set
cat > .dev.vars << 'EOF'
SOCKS5_PROXY_HOSTNAME=localhost
SOCKS5_PROXY_PORT=1080
SOCKS5_PROXY_USERNAME=username
SOCKS5_PROXY_PASSWORD=password
EOFEnvironment variables
| Variable | Purpose |
| ----------------------- | ------------------------------------ |
| SOCKS5_PROXY_HOSTNAME | Proxy hostname for integration tests |
| SOCKS5_PROXY_PORT | Proxy port for integration tests |
| SOCKS5_PROXY_USERNAME | Proxy auth username |
| SOCKS5_PROXY_PASSWORD | Proxy auth password |
License
MIT
