stealth-fetch
v0.1.2
Published
HTTP/1.1 + HTTP/2 client for Cloudflare Workers via cloudflare:sockets, bypassing cf-* header injection
Maintainers
Readme
stealth-fetch
HTTP/1.1 + HTTP/2 client for Cloudflare Workers built on cloudflare:sockets.
It avoids automatic cf-* header injection by using raw TCP sockets.
Highlights
- HTTP/1.1 + HTTP/2 with ALPN negotiation
- WASM TLS (rustls) for TLS 1.2/1.3 + ALPN control
- HTTP/2 connection pooling and protocol cache
- NAT64 fallback for blocked outbound connections
- Redirects, retries, and timeouts
- Raw headers preserved (order + multi-value)
Install
pnpm add stealth-fetchUsage
import { request } from "stealth-fetch";
const response = await request("https://api.example.com/v1/data", {
method: "POST",
headers: {
Authorization: "Bearer sk-...",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: "Hello" }],
}),
});
const data = await response.json();
console.log(data);Web Response Compatibility
If you need a standard Web Response object (with bodyUsed, clone, text,
json, etc.), convert with toWebResponse():
import { request, toWebResponse } from "stealth-fetch";
const resp = await request("https://httpbin.org/headers", { protocol: "h2" });
const webResp = toWebResponse(resp);
const text = await webResp.text();Note: don’t call resp.text()/json()/arrayBuffer() before converting, or the
body stream will already be consumed.
If you need a pre-cloned pair (using ReadableStream.tee()), pass
{ tee: true }:
const { response, clone } = toWebResponse(resp, { tee: true });API
request(url, options?)
Returns Promise<HttpResponse>.
Options
| Option | Type | Default | Description |
| ---------------- | ------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------- |
| method | string | 'GET' | HTTP method |
| headers | Record<string, string> | {} | Request headers |
| body | string \| Uint8Array \| ReadableStream<Uint8Array> \| null | null | Request body |
| protocol | 'h2' \| 'http/1.1' \| 'auto' | 'auto' | Protocol selection |
| timeout | number | 30000 | Overall timeout from call until response headers (includes retries/redirects) |
| headersTimeout | number | — | Timeout waiting for response headers |
| bodyTimeout | number | — | Idle timeout waiting for response body data |
| signal | AbortSignal | — | Cancellation signal |
| redirect | 'follow' \| 'manual' | 'follow' | Redirect handling |
| maxRedirects | number | 5 | Max redirects to follow |
| decompress | boolean | true | Auto-decompress gzip/deflate responses |
| compressBody | boolean | false | Gzip-compress request body (Uint8Array > 1KB) |
| strategy | 'compat' \| 'fast-h1' | 'compat' | compat: ALPN + protocol cache (h2 supported); fast-h1: platform TLS for non-CF, WASM TLS h1 for CF (faster, no h2) |
Response
interface HttpResponse {
status: number;
statusText: string;
headers: Record<string, string>;
rawHeaders: ReadonlyArray<[string, string]>;
protocol: "h2" | "http/1.1";
body: ReadableStream<Uint8Array>;
text(): Promise<string>;
json(): Promise<unknown>;
arrayBuffer(): Promise<ArrayBuffer>;
getSetCookie(): string[];
}Advanced APIs
import {
Http2Client,
Http2Connection,
http1Request,
clearPool,
preconnect,
createWasmTLSSocket,
} from "stealth-fetch";Http2Client— HTTP/2 client with stream multiplexingHttp2Connection— Low-level HTTP/2 connectionhttp1Request(socket, request)— HTTP/1.1 over a raw socketclearPool()— Clear the HTTP/2 connection poolpreconnect(hostname, port?)— Pre-establish an HTTP/2 connectioncreateWasmTLSSocket(hostname, port, alpnList)— WASM TLS socket with ALPN
Differences From fetch
fetchinjectscf-*headers in Workers, this library does not.fetchexposes standardRequest/Responseobjects; this library returns a customHttpResponse(usetoWebResponse()if you need a WebResponse).- Protocol control (force
h1/h2, ALPN, NAT64) is supported here.
NAT64 Fallback Notes
NAT64 is a best-effort fallback. Public NAT64 gateways can be unstable or blocked depending on region and routing, so some connections may fail. If you rely on NAT64, plan for retries or fallback behavior in your application.
Requirements
- Cloudflare Workers runtime
nodejs_compatcompatibility flag
Example Worker
The repo includes a minimal worker at examples/worker.ts with endpoints:
/http1/http2/auto/fetch/single?url=&mode=auto|h1|h2|fetch
wrangler.toml points to examples/worker.ts as the entry.
Development
pnpm dev # Local dev server (wrangler)
pnpm test:run # Run tests once
pnpm test # Run tests in watch mode
pnpm type-check # TypeScript type check
pnpm build # Build to dist/
pnpm lint # ESLint
pnpm format # PrettierContributing
See CONTRIBUTING.md for commit message rules and dev notes.
Building WASM TLS
Requires Rust toolchain with wasm-pack and wasm32-unknown-unknown target:
pnpm build:wasm