Lewati ke konten

Transportasi fetch

fetch() bawaan di Phasis berjalan melalui PHP milik ext-curl — CURLOPT_FOLLOWLOCATION=false (loop pengalihan spesifikasi berjalan di sisi Phasis) dengan CURLOPT_PROGRESSFUNCTION yang memeriksa AbortSignal. Untuk sebagian besar penyematan, ini memadai.

Untuk keperluan lain — proxy, DNS khusus, klien HTTP yang sudah digunakan aplikasi host, sandbox jaringan, atau mock dalam pengujian — transportasinya dapat diganti.

Antarmuka

PHP
public function setFetchTransport(callable $transport): void

Callable Anda menerima:

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

$request adalah array asosiatif:

Kunci Tipe Catatan
method string Sudah diubah menjadi huruf kapital (GET, POST, …)
url string URL parser + rantai pengalihan menghasilkan URL absolut yang sudah diselesaikan
headers list<list<string>> Pasangan [name, value]; kapitalisasi sesuai normalisasi spesifikasi; entri multivalue berupa pasangan terpisah (Set-Cookie, dll.)
body string Byte mentah. String kosong untuk metode tanpa body.
redirect string "follow" / "error" / "manual" (transportasi Anda JANGAN mengikutinya — loop pengalihan berjalan di Phasis)
timeout int Batas waktu dinding dalam milidetik. 0 = tanpa batas.
credentials string "omit" / "same-origin" / "include" — bersifat informatif; cookie dihubungkan secara terpisah melalui setCookieJar().

$signal bernilai null jika permintaan tidak diberi sinyal; jika tidak, nilainya adalah JsObject yang menyediakan aborted dan reason. Periksa secara berkala selama transfer yang lama.

Bentuk nilai yang dikembalikan:

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

Jika transportasi melempar error, Phasis menangkap dan menerjemahkannya:

  • Phasis\BuiltIn\Fetch\TransportException dengan $e->kind === "aborted" → fetch menolak dengan AbortError DOMException.
  • kind === "timeout" → TypeError yang membawa pesan tersebut.
  • kind === "network-error" (bawaan) → TypeError.
  • Nilai lainnya → TypeError dengan pesan pengecualian.

Contoh lengkap

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),
    ];
});

Tolak semua

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

Ini membuat setiap pemanggilan fetch() ditolak dengan TypeError. Berguna untuk sandboxing JS yang diberikan pengguna.

Mock untuk pengujian

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'],
    ];
});

Hook kebijakan

Transportasi menjalankan permintaan; hook kebijakan menentukan apakah permintaan dijalankan. Gunakan untuk penegakan khusus HTTPS, injeksi header, penulisan ulang URL, allowlist, atau pengikatan kredensial per tenant.

PHP
public function setFetchPolicy(callable $hook): void

Tanda tangan hook: function (JsObject $request): ?JsObject. Mengembalikan null berarti mengizinkan tanpa perubahan. Mengembalikan Request berarti menulis ulang. Melempar error akan menolak fetch dengan error tersebut.

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;
});

Hook kebijakan berjalan sebelum transportasi — sehingga transportasi yang bermasalah atau tidak tepercaya pun tidak dapat melewatinya.

Ringkasan perilaku bawaan

Aspek Bawaan
Transportasi CurlTransport (PHP ext-curl)
Pengalihan Ikuti hingga 20 kali sesuai spesifikasi. redirect: "manual" mengembalikan Response pengalihan dengan type: "opaqueredirect".
Batas waktu 30 s waktu dinding, 10 s untuk koneksi
Verifikasi TLS CURLOPT_SSL_VERIFYPEER = true (tidak dapat dinonaktifkan — ganti melalui penggantian transportasi)
User-Agent Phasis/0.1 disisipkan hanya jika belum ditetapkan
Cookie Tidak disimpan. Gunakan setCookieJar() untuk mengaktifkannya.
Hook kebijakan Tidak ada. Semua permintaan diizinkan kecuali ditolak secara eksplisit.
AbortSignal Dipatuhi saat proses berlangsung melalui pemeriksaan callback progres curl.

Lihat juga