Ga naar de inhoud

Fetchtransport

De standaard-fetch() in Phasis loopt via PHP's ext-curl — CURLOPT_FOLLOWLOCATION=false (de redirect-loop uit de specificatie draait in Phasis-land), waarbij CURLOPT_PROGRESSFUNCTION de AbortSignal controleert. Voor de meeste toepassingen is dit prima.

Voor alle andere gevallen — proxy's, aangepaste DNS, de bestaande HTTP-client van de hostapp, netwerksandboxing en mocks in tests — is het transport verwisselbaar.

De interface

PHP
public function setFetchTransport(callable $transport): void

Je callable ontvangt:

PHP
function (array $request, ?\Phasis\Value\JsObject $signal): array

$request is een associatieve array:

Sleutel Type Opmerkingen
method string Al omgezet naar hoofdletters (GET, POST, …)
url string Opgeloste absolute URL na de parser voor URL en de redirectketen
headers list<list<string>> Paren van [name, value]; hoofdlettergebruik volgens de normalisatie van de specificatie; waarden met meerdere waarden vormen aparte paren (zoals Set-Cookie)
body string Ruwe bytes. Lege tekenreeks voor methoden zonder body.
redirect string "follow" / "error" / "manual" (je transport mag deze NIET volgen — de redirect-loop draait in Phasis)
timeout int Budget voor wandkloktijd in milliseconden. 0 = geen limiet.
credentials string "omit" / "same-origin" / "include" — alleen ter informatie; cookies worden afzonderlijk gekoppeld via setCookieJar().

$signal is null als er geen signaal aan het verzoek is meegegeven; anders is het een JsObject met aborted en reason. Controleer dit bij lange overdrachten.

Retourstructuur:

PHP
return [
    'status'     => 200,                          // int
    'statusText' => 'OK',                         // string
    'headers'    => [['content-type', 'text/plain'], ['x-custom', '1']],
    'body'       => '...response bytes...',       // string
];

Als het transport een fout gooit, vangt Phasis deze op en vertaalt deze:

  • Een Phasis\BuiltIn\Fetch\TransportException met $e->kind === "aborted" → fetch wordt afgewezen met AbortError DOMException.
  • kind === "timeout" → TypeError met het bericht.
  • kind === "network-error" (standaard) → TypeError.
  • Al het overige → TypeError met het foutbericht.

Voorbeelden

Guzzle

PHP
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['allow_redirects' => false]);

$engine->setFetchTransport(function (array $req, $signal) use ($client) {
    try {
        $r = $client->request($req['method'], $req['url'], [
            'headers'     => collect($req['headers'])->groupBy(0)->map->pluck(1)->all(),
            'body'        => $req['body'],
            'timeout'     => max(1, $req['timeout'] / 1000.0),
            'http_errors' => false,
        ]);
    } catch (GuzzleException $e) {
        throw new \Phasis\BuiltIn\Fetch\TransportException($e->getMessage(), 'network-error');
    }
    $headers = [];
    foreach ($r->getHeaders() as $name => $values) {
        foreach ($values as $v) {
            $headers[] = [$name, $v];
        }
    }
    return [
        'status'     => $r->getStatusCode(),
        'statusText' => $r->getReasonPhrase(),
        'headers'    => $headers,
        'body'       => (string) $r->getBody(),
    ];
});

Symfony HttpClient

PHP
use Symfony\Component\HttpClient\HttpClient;

$client = HttpClient::create(['max_redirects' => 0]);

$engine->setFetchTransport(function (array $req, $signal) use ($client) {
    $headers = [];
    foreach ($req['headers'] as [$name, $value]) {
        $headers[$name][] = $value;
    }
    $r = $client->request($req['method'], $req['url'], [
        'headers'      => $headers,
        'body'         => $req['body'],
        'timeout'      => $req['timeout'] / 1000.0,
        'max_redirects'=> 0,
    ]);
    return [
        'status'     => $r->getStatusCode(),
        'statusText' => '',
        'headers'    => collect($r->getHeaders(false))
            ->flatMap(fn ($values, $name) => array_map(fn ($v) => [$name, $v], $values))
            ->values()->all(),
        'body'       => $r->getContent(false),
    ];
});

Alles weigeren

PHP
$engine->setFetchTransport(function () {
    throw new \Phasis\BuiltIn\Fetch\TransportException(
        'network access disabled in this sandbox',
        'network-error',
    );
});

Zo wordt elke aanroep van fetch() afgewezen met TypeError. Handig om door gebruikers aangeleverde JS in een sandbox uit te voeren.

Mock voor tests

PHP
$canned = [
    'https://api.example.com/users' => ['status' => 200, 'body' => '{"users":[]}'],
    'https://api.example.com/404'   => ['status' => 404, 'body' => 'not found'],
];

$engine->setFetchTransport(function (array $req) use ($canned) {
    $r = $canned[$req['url']] ?? ['status' => 500, 'body' => 'no mock'];
    return [
        'status'     => $r['status'],
        'statusText' => $r['status'] === 200 ? 'OK' : 'ERR',
        'headers'    => [['content-type', 'application/json']],
        'body'       => $r['body'],
    ];
});

De beleidsfunctie

Het transport voert het verzoek uit; de beleidsfunctie bepaalt of het überhaupt wordt uitgevoerd. Gebruik deze voor handhaving van alleen HTTPS, het toevoegen van headers, het herschrijven van URL, allowlists of het koppelen van inloggegevens per tenant.

PHP
public function setFetchPolicy(callable $hook): void

Handtekening van de functie: function (JsObject $request): ?JsObject. Retourneer null om het verzoek ongewijzigd toe te staan. Retourneer een Request om het te herschrijven. Een gegooide fout zorgt ervoor dat de fetch met die fout wordt afgewezen.

PHP
$allowedHosts = ['api.example.com', 'cdn.example.com'];

$engine->setFetchPolicy(function ($req) use ($allowedHosts) {
    $url = parse_url($req->get('url')->value);
    if (!in_array($url['host'] ?? '', $allowedHosts, true)) {
        throw new \RuntimeException("host not allowed: " . ($url['host'] ?? ''));
    }
    // Inject internal-only auth header
    $req->get('headers')->call('set', 'X-Internal-Token', $secret);
    return null;
});

De beleidsfunctie wordt uitgevoerd vóór het transport — zelfs een transport met fouten of zonder vertrouwen kan deze dus niet omzeilen.

Overzicht van standaardgedrag

Onderdeel Standaard
Transport CurlTransport (PHP ext-curl)
Redirects Volgt er maximaal 20 volgens de specificatie. redirect: "manual" retourneert de redirect-response met type: "opaqueredirect".
Time-out 30 s wandkloktijd, 10 s voor verbinding maken
TLS-verificatie CURLOPT_SSL_VERIFYPEER = true (kan niet worden uitgeschakeld — vervang het transport om dit aan te passen)
User-Agent Phasis/0.1 wordt alleen toegevoegd als deze nog niet is ingesteld
Cookies Worden niet opgeslagen. Gebruik setCookieJar() om ze in te schakelen.
Beleidsfunctie Geen. Alle verzoeken worden toegestaan, tenzij ze expliciet worden geweigerd.
AbortSignal Wordt tijdens de overdracht gecontroleerd via de voortgangsfunctie van curl.

Zie ook