js/globals/fetch

js/globals/fetch.ts

Fetch global and server-side transport.

This module installs the global fetch() function and contains the server-side transport implementation. Some pool-inspection hooks exist for runtime tests, but application code calls the global directly and never imports this module.

Implements Fino's release-supported server-side Fetch baseline: - HTTP and HTTPS support over plain TCP and TLS - HTTP/2 reuse for HTTPS origins that negotiate h2 through ALPN - HTTP/3 over QUIC, forced with protocol: 'h3' or discovered through Alt-Svc response headers - Redirect following with configurable redirect mode - AbortSignal cancellation, including during response body streaming - Subresource integrity checks for buffered response bodies - Explicit referrer and referrerPolicy handling - Response decompression for gzip, deflate, and brotli when available - data: and blob: URL fetches resolved without touching the network - global Request/Response/Headers backed by internal:net/http/wire

Connection lifecycle

HTTP/1 requests open a fresh TCP or TLS connection per hop and request Connection: close. The connection is kept alive until the response body is fully consumed (or the iterator is closed early), at which point the socket is closed. 204/304 and other bodyless responses close the socket immediately after parsing headers. HTTPS requests that negotiate HTTP/2 through ALPN can reuse the resulting H2 session through the origin-keyed pool.

HTTP/3 and Alt-Svc

When libnghttp3 is available, protocol: 'h3' forces the request over QUIC. In 'auto' mode an HTTPS response carrying an Alt-Svc: h3=... header populates an origin-keyed cache (honouring the ma max-age parameter), and later requests to that origin with replayable bodies (string, Uint8Array, ArrayBuffer, or none) are first attempted over a pooled H3 session. If the QUIC attempt fails, the cache entry is evicted and the request silently falls back to TCP.

Redirect handling

The redirect init option controls redirect behaviour: - 'follow' (default) — follow up to 20 redirects, then throw TypeError - 'error' — throw TypeError on any redirect response - 'manual' — return an opaque redirect response (status 0, empty headers, null body)

Method changes on redirect: - 301, 302, 303: method → GET, body dropped - 307, 308: method kept; body must be replayable — a streaming (async-iterable or ReadableStream) body throws TypeError

A redirect status without a Location header is returned as a normal response. Redirect targets must be http: or https: URLs; anything else (javascript:, file:, data:, ...) throws TypeError.

AbortSignal

signal is raced against every async operation: DNS lookup, TCP connect, TLS handshake, and response parsing. After fetch() returns, the wrapped body iterator also checks signal.aborted on each .next() call so that a long streaming response can be cancelled mid-stream.

Request safety

Requests to a known list of unsafe ports (SMTP, IRC, NFS, ...) are rejected with a TypeError, mirroring the WHATWG bad-port list. The CONNECT, TRACE, and TRACK methods are forbidden. GET and HEAD requests reject when given a body.

Browser policy non-parity

Authorization, Cookie, and Cookie2 are stripped when following a redirect to a different origin. Otherwise this is a server-side transport API, not a browser policy engine. mode, credentials, cache, and keepalive are accepted as compatibility fields, but they do not enforce CORS, create opaque no-cors responses, maintain a browser cookie jar, reuse cached responses, extend upload lifetime after shutdown, or synthesize a default browser referrer. Caller-provided Cookie and authorization headers remain explicit request headers until a cross-origin redirect strips them.

Usage

const res = await fetch('https://example.com/data');
const json = await res.json();

const posted = await fetch('https://example.com/items', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ name: 'widget' }),
  signal: AbortSignal.timeout(5000),
  redirect: 'follow',
});

WHATWG Fetch Standard: https://fetch.spec.whatwg.org/

Interfaces

interface FetchInit {

Options accepted by the global fetch() function.

Fino accepts the standard request init fields used by browser and server Fetch implementations, plus transport-specific tls, protocol, and request trailers options for runtime-managed HTTP clients. Fields set here override the corresponding fields of a Request passed as input.

const res = await fetch('https://internal.example/report', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ ok: true }),
  signal: AbortSignal.timeout(10_000),
  redirect: 'error',
  tls: { ca: '/etc/ssl/internal-ca.pem' },
  protocol: 'h2',
});

Properties

method?: string

Request method. Common methods are normalized to uppercase before sending; unknown methods are sent verbatim. CONNECT, TRACE, and TRACK throw a TypeError.

headers?: Headers | string[][] | Record<string, string> | null | undefined

Request headers as a Headers instance, an array of [name, value] pairs, or a plain name-to-value record. Host, Connection, and Accept-Encoding are auto-injected for HTTP/1 requests when absent.

body?: unknown

Request body value: a string, byte buffer, Blob, FormData, URLSearchParams, async iterable of Uint8Array, or ReadableStream. Streaming bodies are one-shot — they cannot be replayed across 307/308 redirects and disable automatic Alt-Svc HTTP/3 upgrades. GET and HEAD requests must not have a body.

signal?: AbortSignal | null

Abort signal used to cancel connection setup, redirects, uploads, and streaming response reads. Rejects with signal.reason.

redirect?: 'follow' | 'error' | 'manual'

Redirect handling mode. 'follow' (default) chases up to 20 redirects, 'error' throws a TypeError on any redirect status, and 'manual' returns an opaque redirect response (status 0, empty headers, null body).

integrity?: string

Subresource integrity metadata (e.g. sha256-<base64>; sha256, sha384, and sha512 are supported). The response body is buffered in full and the digest verified before the Response is returned; a mismatch throws a TypeError. Silently skipped when libcrypto is unavailable.

referrerPolicy?: 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url' | ''

Referrer policy used when constructing the outgoing Referer header from referrer. Defaults to 'strict-origin-when-cross-origin'. Only applies when an explicit referrer is provided — Fino never synthesizes a default browser referrer.

referrer?: string

Explicit referrer URL reduced through referrerPolicy into a Referer header. 'no-referrer' suppresses the header; 'about:client' is ignored (no header is sent).

mode?: 'cors' | 'no-cors' | 'same-origin' | 'navigate'

Browser compatibility field. Fino accepts it but does not enforce CORS or synthesize opaque responses.

credentials?: 'omit' | 'same-origin' | 'include'

Browser compatibility field. Fino does not maintain a browser cookie jar.

cache?: 'default' | 'no-store' | 'reload' | 'no-cache' | 'force-cache' | 'only-if-cached'

Browser compatibility field. Fino does not maintain an HTTP cache.

keepalive?: boolean

Browser compatibility field. Fino accepts it without extending request lifetime after runtime shutdown.

priority?: 'high' | 'low' | 'auto'

Request priority hint. Validated ('high', 'low', or 'auto') but not currently used for scheduling.

trailers?: Headers | (() => Headers | Promise<Headers>)

HTTP trailers sent after the request body: either a Headers instance or a function (sync or async) called after the body is written, so trailer values can be computed from the streamed content.

tls?: { ca?: string; rejectUnauthorized?: boolean; cert?: string; key?: string; }

TLS client and trust options for HTTPS connections. ca, cert, and key are paths to PEM files; rejectUnauthorized defaults to true. Requests carrying a client certificate are pooled separately from anonymous connections to the same origin.

protocol?: 'auto' | 'http/1.1' | 'h2' | 'h3'

Preferred HTTP protocol. 'auto' (default) negotiates: ALPN picks h2 or HTTP/1.1, and a cached Alt-Svc entry may upgrade to h3. 'h2' requires ALPN to negotiate h2 and throws otherwise; 'h3' requires an HTTPS URL and libnghttp3. 'http/1.1' pins the request to HTTP/1.

Functions

async function fetch(input: string | Request, init?: FetchInit): Promise<Response>

Fetch a resource over HTTP or HTTPS.

Follows the WHATWG Fetch API core flow with Fino Request, Response, and Headers objects. input is a URL string (resolved against globalThis.location when relative and a location is set) or a Request whose fields serve as defaults for init. data: and blob: URLs are resolved in-process without network I/O; blob: fetches support GET only, including Range requests. The returned Response carries the final URL after redirects and redirected metadata.

Redirect mode defaults to 'follow', up to 20 hops, and cross-origin redirects strip Authorization and Cookie headers. GET and HEAD requests reject when given a body. Requests to unsafe ports and the CONNECT, TRACE, and TRACK methods throw a TypeError. AbortSignal cancellation is checked before the request starts, during DNS/connect/TLS/parse, and while reading the response body.

The returned Response may have a streamed body. If the body is not consumed or closed, the underlying HTTP/1 socket can remain open until the runtime tears it down. HTTPS requests may reuse an HTTP/2 session when ALPN negotiates h2, or an HTTP/3 session when protocol: 'h3' is set or a cached Alt-Svc entry exists. Integrity checks buffer the full body before returning a Response. Compressed bodies (gzip, deflate, brotli) are transparently decompressed and the Content-Encoding/Content-Length headers removed.

Browser policy knobs are intentionally limited in this release: CORS, credentials, cache, cookies, keepalive lifetime, and default referrer behavior are not enforced by the runtime. mode: "no-cors" still returns a normal response, credentials never creates an implicit cookie jar, cache never reuses a prior response, and keepalive does not extend work beyond normal runtime lifetime. Explicit referrer and referrerPolicy values are converted to a Referer header when supported.

const response = await fetch('https://example.com/data.json', {
  headers: { accept: 'application/json' },
  signal: AbortSignal.timeout(5000),
  redirect: 'follow',
});
if (response.ok) {
  const payload = await response.json();
  console.log(payload);
}

// Stream a large download, cancellable mid-body:
const controller = new AbortController();
const download = await fetch('https://example.com/archive.bin', {
  signal: controller.signal,
});
let received = 0;
for await (const chunk of download.body!) {
  received += chunk.byteLength;
  if (received > 100_000_000) controller.abort(new Error('too large'));
}