Panggilan API yang gagal punya dua wajah. Wajah pertama: server menjawab dengan error — mengecewakan, tetapi cepat dan jelas. Wajah kedua jauh lebih berbahaya: server diam, jaringan menggantung, dan request kita menunggu tanpa batas sambil menahan worker queue, proses command, atau request user yang kebetulan lewat. Satu endpoint lambat bisa melumpuhkan seluruh aplikasi. Chapter ini membekali setiap panggilan dengan dua pengaman: timeout() yang membatasi lamanya menunggu, dan retry() yang mengulang panggilan yang gagal sementara.
timeout dan connectTimeout #
Dua method ini mengatur dua momen berbeda dalam hidup sebuah request:
->timeout($detik)— batas total waktu untuk keseluruhan request, termasuk menunggu response.->connectTimeout($detik)— hanya batas waktu untuk membangun koneksi TCP; setelah tersambung, batas ini tidak berlaku.
Tanpa timeout(), HTTP Client menunggu selama default Guzzle ( behaviour no limit praktis untuk kebanyakan kasus). Untuk membuktikan efeknya, kita jalankan server kecil di 127.0.0.1:8099 yang sengaja menunda response dua detik — server php -S dengan satu baris sleep(2):
<?php
use Illuminate\Support\Facades\Http;
$mulai = microtime(true);
try {
Http::timeout(1)->get('http://127.0.0.1:8099/lambat');
} catch (Illuminate\Http\Client\ConnectionException $e) {
echo 'terlempar: '.get_class($e)."\n";
echo 'pesan: '.$e->getMessage()."\n";
}
printf("durasi: %.1f detik\n", microtime(true) - $mulai);Output yang Dihasilkan:
terlempar: Illuminate\Http\Client\ConnectionException
pesan: cURL error 28: Operation timed out after 1001 milliseconds with 0 bytes
received (see https://curl.se/libcurl/c/libcurl-errors.html) for
http://127.0.0.1:8099/lambat
durasi: 1.0 detikServer butuh dua detik untuk menjawab, tetapi request kita berhenti di detik pertama dengan ConnectionException. Angka 1001 milliseconds bisa bergeser beberapa milidetik antar eksekusi — intinya cURL memotong di sekitar batas satu detik. Sebagai aturan praktis, set timeout(5) atau timeout(10) untuk API biasa dan angka yang lebih ketat di dalam request web yang user tunggu langsung.
retry: Mengulang yang Gagal Sementara #
Server yang sibuk sering hanya butuh diberi kesempatan kedua. retry(jumlah, jedaMilidetik) mengulang request secara otomatis saat response gagal atau koneksi terputus:
<?php
use Illuminate\Support\Facades\Http;
$percobaan = 0;
Http::fake(function () use (&$percobaan) {
$percobaan++;
if ($percobaan < 3) {
return Http::response(['pesan' => 'server sibuk'], 500);
}
return Http::response(['pesan' => 'oke'], 200);
});
$response = Http::retry(3, 0)->get('https://api.example.com/proyek');
echo 'status: '.$response->status()."\n";
echo 'pesan: '.$response->json('pesan')."\n";
echo 'total request terkirim: '.$percobaan."\n";Output yang Dihasilkan:
status: 200
pesan: oke
total request terkirim: 3Dua request pertama mendapat 500, request ketiga berhasil — dan counter $percobaan di dalam fake membuktikan tiga request benar-benar terkirim.
Parameter kedua tidak harus angka tetap. Berikan closure yang menerima nomor percobaan dan mengembalikan jeda dalam milidetik, dan kita mendapat exponential backoff — jeda yang membesar supaya server yang sedang kewalahan tidak makin diperas:
<?php
$jeda = [];
$response = Http::retry(3, function (int $attempt) use (&$jeda) {
$jeda[] = 100 * $attempt;
return 100 * $attempt;
})->get('https://api.example.com/proyek');
echo 'jeda antar percobaan (ms): '.implode(', ', $jeda)."\n";Output yang Dihasilkan (request pertama dan kedua gagal, seperti contoh sebelumnya):
jeda antar percobaan (ms): 100, 200Setelah percobaan pertama gagal HTTP Client menunggu 100 ms, setelah percobaan kedua 200 ms — memberi napas yang makin panjang pada setiap kegagalan.
Hanya Retry yang Masuk Akal #
Tidak semua kegagalan pantas diulang: 404 tetap 404 sebanyak apa pun diulang, dan 422 berarti payload kita memang salah. Parameter ketiga retry() menerima callback yang memutuskan boleh-tidaknya mengulang:
<?php
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
$percobaan = 0;
Http::fake(function () use (&$percobaan) {
$percobaan++;
return Http::response(['pesan' => 'tidak ditemukan'], 404);
});
try {
Http::retry(3, 0, function (Exception $exception) {
return $exception instanceof RequestException
&& $exception->response->status() === 500;
})->get('https://api.example.com/proyek/999');
} catch (RequestException $e) {
echo 'terlempar: '.get_class($e)."\n";
echo 'status: '.$e->response->status()."\n";
}
echo 'total request terkirim: '.$percobaan."\n";Output yang Dihasilkan:
terlempar: Illuminate\Http\Client\RequestException
status: 404
total request terkirim: 1Callback memeriksa status response dan hanya mengizinkan retry untuk 500; karena server menjawab 404, request tidak pernah diulang dan RequestException langsung terlempar. Perhatikan juga perilaku penting di sini: secara bawaan, retry() melempar RequestException untuk response gagal setelah percobaan habis — jadi rantai retry() dan throw() saling melengkapi secara otomatis. Bila kita ingin menangani response gagal secara manual tanpa exception, matikan lewat parameter keempat: retry(3, 100, when: ..., throw: false).
Yang terakhir adalah kegagalan jaringan murni — server tidak menjawab sama sekali. HTTP Client menandainya dengan ConnectionException, dan retry mengulangnya juga:
<?php
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
Http::fake(fn () => throw new ConnectionException('Koneksi gagal'));
try {
Http::retry(2, 0)->get('https://api.example.com/proyek');
} catch (ConnectionException $e) {
echo 'terlempar: '.get_class($e)."\n";
echo 'pesan: '.$e->getMessage()."\n";
}Output yang Dihasilkan:
terlempar: Illuminate\Http\Client\ConnectionException
pesan: Koneksi gagalDengan timeout dan retry, panggilan kita tahan banting. Namun response — baik yang sukses maupun gagal — masih perlu dibaca dengan cara yang benar: status, header, JSON, sampai exception yang rapi. Itu urusan chapter Response Handling . Daftar lengkap opsi ketahanan tersedia di dokumentasi HTTP Client .