Menggunakan client coroutine Swoole untuk HTTP, TCP, dan database, lalu membangun aggregator API yang memanggil banyak upstream secara parallel dengan WaitGroup dan Channel tanpa memblokir worker.

Setelah di episode 8 kita belajar berkomunikasi antar coroutine lewat Channel dan WaitGroup — pada episode kali ini kita memakainya untuk hal paling praktis di aplikasi nyata: memanggil layanan lain. Aplikasi modern jarang berdiri sendiri; mereka memanggil REST API, membaca TCP stream, menulis ke Redis, dan bertanya ke database.
Mengapa episode ini penting? Karena di sinilah coroutine benar-benar menunjukkan taringnya. Aplikasi PHP-FPM memanggil 3 upstream secara berurutan — total waktu = penjumlahan ketiganya. Dengan client coroutine, 3 panggilan bisa jalan parallel dan total waktu hanya sebesar panggilan terlama. Latensi aplikasi kalian bisa turun berkali-kali lipat tanpa mengubah arsitektur.
Swoole\Coroutine\Http\Client adalah client HTTP coroutine: tidak memblokir worker saat menunggu respons.
use Swoole\Coroutine;
Coroutine::create(function () {
$client = new Coroutine\Http\Client('api.example.com', 443, true);
$client->set(['timeout' => 5]);
$client->get('/users/42');
if ($client->statusCode === 200) {
$data = json_decode($client->body, true);
echo "Nama: {$data['name']}\n";
} else {
echo "Error: {$client->statusCode}\n";
}
$client->close();
});Hal penting:
new Client($host, $port, $ssl) — $ssl = true untuk HTTPS.set() untuk konfigurasi (timeout, header default, dst).get()/post(), hasil ada di properti: statusCode, body, headers, errCode.close() setelah selesai untuk mengembalikan koneksi.Versi singkat memakai helper Coroutine\Http\get() (yang kita pakai di episode 8):
$resp = Coroutine\Http\get('https://api.example.com/data');
echo $resp->getBody();$client = new Coroutine\Http\Client('api.example.com', 443, true);
$client->setHeaders([
'Content-Type' => 'application/json',
'Authorization' => 'Bearer token',
]);
$client->post('/users', json_encode(['name' => 'Andi']));Untuk protokol custom atau layanan yang tidak HTTP (game server, device IoT, protokol internal), pakai client TCP/UDP:
use Swoole\Coroutine;
Coroutine::create(function () {
$tcp = new Coroutine\Client(SWOOLE_SOCK_TCP);
if (!$tcp->connect('127.0.0.1', 9000, 3)) {
echo "Gagal konek: {$tcp->errCode}\n";
return;
}
$tcp->send("PING");
$response = $tcp->recv();
echo "Balasan: $response\n";
$tcp->close();
});Ganti SWOOLE_SOCK_TCP dengan SWOOLE_SOCK_UDP untuk UDP. Client jenis ini sangat penting untuk episode 21 (TCP/UDP & game/IoT servers) — kalian sudah menguasai dasarnya sekarang.
Kita akan membahas pool dan koneksi database secara mendalam di episode 13. Untuk saat ini, kenali bahwa client coroutine memungkinkan query database tanpa memblokir worker — fondasi yang membuat ribuan request concurrent tetap responsif:
use Swoole\Coroutine;
Coroutine::create(function () {
// MySQL
$db = new Coroutine\MySQL();
$db->connect(['host' => '127.0.0.1', 'user' => 'root', 'password' => 'secret', 'database' => 'app']);
$rows = $db->query('SELECT id, name FROM users LIMIT 5');
// Redis
$redis = new Coroutine\Redis();
$redis->connect('127.0.0.1', 6379);
$redis->set('kunci', 'nilai');
echo $redis->get('kunci');
});Tip
Koneksi coroutine MySQL/Redis membutuhkan build option --enable-mysqlnd saat kompilasi (episode 3). Cek dengan php --ri swoole — pastikan swoole.use_mysqli atau swoole.use_mysqlnd bernilai On, kalau tidak client akan melempar error.
gRPC adalah protokol RPC berbasis HTTP/2 + protobuf yang sangat populer untuk microservices. Swoole menyediakan dukungan client gRPC melalui kelas Swoole\Coroutine\Http2\Client (di episode 16 kita bangun server gRPC-nya):
use Swoole\Coroutine;
Coroutine::create(function () {
$grpc = new Coroutine\Http2\Client('svc.internal', 50051);
$grpc->connect();
$req = new Coroutine\Http2\Request;
$req->method = 'POST';
$req->path = '/hello.Greeter/SayHello';
$req->headers['content-type'] = 'application/grpc';
$req->data = pack('N1', 0) . pack('N1', 0) . '{"name":"Andi"}';
$grpc->send($req);
$resp = $grpc->recv();
echo $resp->data;
});Sekarang kita gabungkan semuanya. Kita bangun endpoint /dashboard yang mengambil 4 sumber data (user, orders, stats, notifikasi) — secara parallel:
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Coroutine;
use Swoole\Coroutine\Http\Client;
use Swoole\Coroutine\WaitGroup;
$server = new Server('0.0.0.0', 9501);
$server->on('Request', function (Request $req, Response $res) {
$start = microtime(true);
$wg = new WaitGroup();
$data = [];
$calls = [
'user' => 'https://api.users.local/v1/me',
'orders' => 'https://api.orders.local/v1/recent',
'stats' => 'https://api.stats.local/v1/overview',
'notif' => 'https://api.notif.local/v1/unread',
];
foreach ($calls as $key => $url) {
$wg->add();
Coroutine::create(function () use ($wg, $key, $url, &$data) {
try {
$resp = Coroutine\Http\get($url);
$data[$key] = $resp->getStatusCode() === 200
? json_decode($resp->getBody(), true)
: null;
} catch (Throwable $e) {
$data[$key] = null;
} finally {
$wg->done();
}
});
}
$wg->wait();
$elapsed = round((microtime(true) - $start) * 1000, 2);
$data['latency_ms'] = $elapsed;
$res->header('Content-Type', 'application/json');
$res->end(json_encode($data));
});
$server->start();Perhatikan: $data[$key] = null pada error menjaga struktur tetap konsisten, dan try/finally menjamin done() selalu dipanggil. Hasilnya: kalau tiap upstream butuh 200ms, total endpoint ini ~200ms — bukan 800ms.
| Masalah | Penyebab | Solusi |
|---|---|---|
client->body kosong | Timeout / koneksi gagal | Cek statusCode dan errCode |
| Memory bertambah tiap call | close() tidak dipanggil | Selalu close() atau pakai pool (episode 13) |
| Semua upstream jalan berurutan | Client dipakai di luar coroutine / tanpa WaitGroup | Pastikan setiap call dibungkus coroutine + WaitGroup |
Cannot connect to server | Port salah / firewall | Verifikasi dengan nc -vz host port |
Pada episode 9 ini, kalian telah menguasai client coroutine dan pola parallel.
Inti yang harus dibawa pulang:
Coroutine\Http\Client untuk HTTP(S); cek statusCode, body, errCode setelah panggilan.Coroutine\Client (TCP/UDP) untuk protokol custom — fondasi game/IoT (episode 21).--enable-mysqlnd.finally { $wg->done(); } dan tangani error per-call agar satu kegagalan tidak menggagalkan semuanya.Di episode 10 selanjutnya, kita mengotomasi hal-hal berulang dan memakai banyak proses: Timers & Process — swoole_timer_tick/after, pola cron-like scheduling, dan inter-process communication. Sampai jumpa di episode 10!