Fetch controls outbound HTTP requests

link-fetch is the one HTTP client Link's own calls go out on: to the account service, to a relay, to a service of Link's. Every call gets the same rules, and only typed data crosses the wire. What an agent fetches (a page, a model) doesn't go through it.

The rules every call gets

Client is the client calls go out on, built by a Builder that says what it allows, and it carries these rules to each call (Request).

Its user agent and timeouts

Link's user agent, lnk/<version> (USER_AGENT), and timeouts: TIMEOUT, 30 seconds for a whole call, connecting to the last byte of its answer, unless the caller says another, and CONNECT_TIMEOUT, 10 seconds to connect.

No redirects

A redirect is an error (Error::Redirected): one would carry a credential, or a call, somewhere else.

https:// only

Or http:// to this machine (is_this_machine), a relay or a service of one's own for development: anywhere else a credential would cross the network in the clear (Error::Insecure). A plain http:// call goes through no proxy, and its name is never asked of DNS: localhost and the names under it are this machine's loopback addresses (Loopback), so a network's DNS can't send it elsewhere.

An answer's size capped

MAX_ANSWER, 1 MiB, or the call's own Request::limit, read so: a longer one is an error, never read whole first. Streamed reads an answer as a stream instead.

Typed both ways

A request's body is a Serialize type, an answer a Deserialize type (Request::answer), and every answer is closed: a field its type doesn't name is refused as deny_unknown_fields would, whether or not the type says so. An answer that doesn't parse is an error naming the route (Error::Unexpected), never a guess.

Errors map once

401 to signed out (Error::SignedOut), 429 to try again later (Error::Later, with its Retry-After), a 5xx to the service's fault (Error::Failed), any other refusal with what it said (Error::Refused). None prints a credential, a query string or a value the answer held: a route is named without a secret (Route).

Credentials as Secrets

Credentials go in as Secrets (Request::bearer, Request::basic), into headers marked sensitive; nothing here prints one.

Where a call may go

HttpsUrl and SendUrl are the URLs a setting names, checked as it's read: this machine's addresses and public ones, and how a host is found (Loopback).

blocking

The same client, for a program that doesn't run on a runtime of its own (a command, a sync service): every call made as Client makes it, on a runtime this client keeps, and waited for. The same rules hold, the same errors come back. Like reqwest's own, it can't be called from a task on another runtime.

The audit

Every folder runs it over its own code, from a test of its own (cargo test -p link-fetch --test clients for this one): an HTTP client is built only in link-fetch, or where the folder's list says why (a model's provider, what an agent fetches, a test's fixture). A new one fails until it's made a Client or listed, which a review sees.

What it finds

In code that isn't a test's (a file under a tests/ folder, or from a #[cfg(test)] module on), however it's laid out over lines: reqwest's client made (Client::new(), ::builder(), ::default(), the blocking ones too) or its ClientBuilder named at all, by reqwest's path, by a name a use of reqwest brought in, or through the crate renamed (use reqwest as http).

What it can't see

Being a reading of the text and not of Rust's names: a client made in a macro's expansion, through another crate that re-exports reqwest or wraps its client, through a type alias (type Http = reqwest::Client), or by Default::default() where only the type says what it is. A review sees those.

clients.toml

The list is TOML: [clients] maps a file, relative to the folder, to why its client isn't one of Link's own calls.

Why it works this way

Why Link's calls share one client, and read answers closed, is on Fetch Decisions.