Skip to content

Transport conformance ​

Each behaviour below runs the same test code on every adapter rust-fetch has: both Node.js transports, fetch() in Node.js (the browser build without the Node.js entry), and real browsers. ✓ is the expected behaviour; "differs" is a documented difference, explained under the table. Generated by npm run test:conformance (wasm-module/js/test/conformance/), which fails CI on any other difference. The Rust client is covered by its own integration tests.

Redirects ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
GET through a 301 is followed✓✓✓✓✓✓
GET through a 302 is followed✓✓✓✓✓✓
GET through a 303 is followed✓✓✓✓✓✓
GET through a 307 is followed✓✓✓✓✓✓
GET through a 308 is followed✓✓✓✓✓✓
POST through a 301 becomes GET, without a body✓✓✓✓✓✓
POST through a 302 becomes GET, without a body✓✓✓✓✓✓
POST through a 303 becomes GET, without a body✓✓✓✓✓✓
POST through a 307 becomes POST with its body✓✓✓✓✓✓
POST through a 308 becomes POST with its body✓✓✓✓✓✓
PUT through a 303 becomes GET without a body✓✓✓✓✓✓
Authorization is dropped on a cross-origin redirect, kept on a same-origin one✓✓✓✓✓✓
sensitiveHeaders are dropped on a cross-origin redirect✓✓differs (1)differs (1)differs (1)differs (1)
maxRedirects: 2 stops a third redirect✓✓differs (2)differs (2)differs (2)differs (2)
A redirect loop fails✓✓✓✓✓✓
redirect: 'manual' returns the 3xx✓✓✓differs (3)differs (3)differs (3)
redirect: 'error' rejects a redirect✓✓✓✓✓✓
A redirect to another scheme (data:) is refused✓✓✓✓✓✓

Decompression ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
gzip is decoded✓✓✓✓✓✓
deflate is decoded✓✓✓✓✓✓
br is decoded✓✓✓✓✓✓
A corrupt gzip body fails✓✓✓✓✓differs (4)
A 64 MB gzip bomb stops at maxResponseBytes✓✓✓✓✓✓

Size limits ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
maxResponseBytes: exactly at the limit passes, one byte more fails✓✓✓✓✓✓
maxResponseBytes errors a streamed body once it passes the limit✓✓✓✓✓✓

Timeouts and cancellation ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
A timeout before the headers is a TimeoutError✓✓✓✓✓✓
The timeout covers reading the body✓✓✓✓✓✓
An already-aborted signal rejects without sending✓✓✓✓✓✓
Aborting while waiting for the response is a CancelledError✓✓✓✓✓✓
Aborting mid-stream errors the body with a CancelledError✓✓✓✓✓✓

Errors ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
A refused connection is a NetworkError✓✓✓✓✓✓
A 404 is an HttpError with the status✓✓✓✓✓✓
Malformed JSON falls back to text by default✓✓✓✓✓✓
Malformed JSON with responseType: 'json' is a ParseError✓✓✓✓✓✓
CR/LF in a header value is an InvalidInputError, before sending✓✓✓✓✓✓
A 503 is retried, and the retry succeeds✓✓✓✓✓✓

Streaming ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
A streamed body arrives in full✓✓✓✓✓✓
A connection dropped mid-body errors the stream with a NetworkError✓✓✓✓✓✓
A slow reader of a streamed body holds the server back, and cancel() closes the connection✓✓✓differs (5)differs (5)differs (5)
A slow reader of NDJSON holds the server back, and cancel() closes the connection✓✓✓differs (5)differs (5)differs (5)
Aborting mid-stream closes the connection✓✓✓✓✓✓
maxResponseBytes reached mid-stream closes the connection✓✓✓✓✓✓
NDJSON: \n and \r\n, empty lines skipped, a last line without a newline✓✓✓✓✓✓
maxLineBytes: the lines before a long one, then a NetworkError✓✓✓✓✓✓
Server-Sent Events: types, ids and multi-line data, until [DONE]✓✓✓✓✓✓
maxEventBytes: the events before a large one, then a NetworkError✓✓✓✓✓✓

URLs ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
baseUrl joining, relative paths and query parameters encode the same everywhere✓✓✓✓✓✓

Security ​

Behaviournode:httpnativefetch() in Node.jsChromiumFirefoxWebKit
Paths like //host stay under baseUrl✓✓✓✓✓✓
allowAbsoluteUrls: false refuses an absolute URL, before sending✓✓✓✓✓✓
allowPrivateNetwork: false blocks 127.0.0.1, before sending✓✓differs (6)differs (6)differs (6)differs (6)
maxPendingRequests refuses one more, without sending it✓✓✓✓✓✓
retryBudget refuses a retry, keeping the attempt’s error✓✓✓✓✓✓

Documented differences ​

  1. fetch() follows redirects itself, so sensitiveHeaders can't apply (documented)
  2. fetch() has no redirect-limit option; it applies its own limit (20) instead (documented)
  3. browsers return an opaque redirect: status 0 and an empty body (documented)
  4. WebKit decodes responses itself: on macOS a corrupt gzip body fails, but on Linux (libsoup) it's passed through as it arrived, which rust-fetch can't detect
  5. browsers read a response ahead of the page themselves, however slowly it's consumed: their own fetch() took 59–64 MB of 64 in this test, as rust-fetch did; how far varies by browser and machine
  6. only the Node.js entry checks addresses; browsers enforce their own private-network rules (documented)

Released under the MIT or Apache-2.0 license.