API / Minn / Http

Http final readonly class

Minn\Http · public/minn/src/Minn/Http.php · 184 lines

Outgoing HTTP, a client handed to the code that uses it. An extension finds one on $minn->http, bound to the hosts its minn.json asks for under needs.http; an engine class takes one in its constructor, the real network unless a test passes a fake's:

$data = $minn->http->get('https://api.example.com/status')->json(); $this->http->post($url, json: ['email' => $email], timeout: 10)->throw();

Every verb answers with an Exchange (->ok(), ->failed(), ->json(), ->header(), ->cookie(), ->throw(), ->code, ->body) and takes the same named arguments, each optional:

- `query`: array added to the URL's query string - `headers`: `name => value` (a list value repeats the header), or "Name: value" lines - `json`, `form`, `body` (post, put, patch): an array sent as JSON, an array form-encoded, or a string sent as it is; one of the three, and the first two set the Content-Type - `timeout`: seconds for the whole exchange, 5 by default - `redirects`: how many to follow, 5 by default; 0 hands back the 3xx - `hosts`: URL prefixes every hop must start with (`['https://api.github.com/']`) - `private`: hosts allowed to reach a private address. Loopback, LAN, link-local and cloud-metadata addresses are refused otherwise, at every hop and after DNS, so a URL someone typed is safe to fetch - `maxBytes`: the largest body accepted; a longer one fails the exchange - `userAgent`: Minn/{version} by default

A misspelt argument is an error at the line that has it. Credentials (Authorization, Cookie) are dropped when a redirect leaves the origin. A fake (new Http\Fake([...])->http()) answers only the client it made, so a test shares nothing with the rest of the process.

Constants and properties

privateREDIRECTS = array ( 0 => 301, 1 => 302, 2 => 303, 3 => 307, 4 => 308, )
privateCREDENTIALS = array ( 0 => 'authorization', 1 => 'cookie', 2 => 'proxy-authorization', )
privatereadonly array $within

Promoted from the constructor.

privatereadonly ?Minn\Http\Fake $fake

Promoted from the constructor.

privatereadonly ?string $refusal

Promoted from the constructor.

Public

new__construct(array $within = array ( ), ?Minn\Http\Fake $fake = NULL, ?string $refusal = NULL)#

The network, or $fake answering in its place. With $within, every hop of every request must also start with one of those URL prefixes, whatever a call's own hosts: say (an extension's needs.http).

$withinlist<string>
staticrefused(string $why): self#

A client that throws $why at every call: what an extension holds when its minn.json did not ask for http.

methodget(string $url, array $query = array ( ), array $headers = array ( ), float $timeout = 5.0, int $redirects = 5, array $hosts = array ( ), array $private = array ( ), ?int $maxBytes = NULL, ?string $userAgent = NULL): Minn\Http\Exchange#

A GET.

methodhead(string $url, array $query = array ( ), array $headers = array ( ), float $timeout = 5.0, int $redirects = 5, array $hosts = array ( ), array $private = array ( ), ?string $userAgent = NULL): Minn\Http\Exchange#

A HEAD: the status and headers a GET would bring, without the body.

methoddelete(string $url, array $query = array ( ), array $headers = array ( ), float $timeout = 5.0, int $redirects = 5, array $hosts = array ( ), array $private = array ( ), ?int $maxBytes = NULL, ?string $userAgent = NULL): Minn\Http\Exchange#

A DELETE.

methodpost(string $url, ?array $json = NULL, ?array $form = NULL, ?string $body = NULL, array $query = array ( ), array $headers = array ( ), float $timeout = 5.0, int $redirects = 5, array $hosts = array ( ), array $private = array ( ), ?int $maxBytes = NULL, ?string $userAgent = NULL): Minn\Http\Exchange#

A POST with a json:, form: or body: payload.

methodput(string $url, ?array $json = NULL, ?array $form = NULL, ?string $body = NULL, array $query = array ( ), array $headers = array ( ), float $timeout = 5.0, int $redirects = 5, array $hosts = array ( ), array $private = array ( ), ?int $maxBytes = NULL, ?string $userAgent = NULL): Minn\Http\Exchange#

A PUT with a json:, form: or body: payload.

methodpatch(string $url, ?array $json = NULL, ?array $form = NULL, ?string $body = NULL, array $query = array ( ), array $headers = array ( ), float $timeout = 5.0, int $redirects = 5, array $hosts = array ( ), array $private = array ( ), ?int $maxBytes = NULL, ?string $userAgent = NULL): Minn\Http\Exchange#

A PATCH with a json:, form: or body: payload.

methodsend(Minn\Http\Outbound $request): Minn\Http\Exchange#

Sends exactly what the Outbound says, with none of the verbs' rules about where a request may go: the door WordPress's own HTTP API comes through after applying its rules. A client bound to hosts refuses it, since curl follows a sent request's redirects where no rule can see.

Internals 8

Private and protected methods: how the class does its work. Not a contract; they change whenever the code is tidied.

privatefollow(Minn\Http\Outbound $request, int $redirects, Minn\Http\Destination $where): Minn\Http\Exchange#

Sends each hop, judging every one against the destination rules, until a response is not a redirect.

privatedeliver(Minn\Http\Outbound $request, Minn\Http\Destination $where): Minn\Http\Exchange#
privatethrowIfRefused(): void#
privatenextHop(Minn\Http\Outbound $request, int $status, string $url): Minn\Http\Outbound#

The request a redirect asks for: 303, and 301/302 after a POST, become a GET without the body, as browsers and curl do.

privateoutbound(string $method, string $url, array $query, array $headers, array $payload, float $timeout, ?int $maxBytes, ?string $userAgent): Minn\Http\Outbound#
$payloadarray{0: ?string, 1: ?string}the body and the content type it implies
privatepayload(?array $json, ?array $form, ?string $body): array#
@return array{0: ?string, 1: ?string} the body and the content type it implies
privatelines(array $headers, ?string $type): array#

Headers as the transport's "Name: value" lines, with the payload's Content-Type unless one was given.

@return list<string>
privatewithQuery(string $url, array $query): string#