Belajar Swoole - Coroutine Clients (HTTP, TCP, gRPC)
Episode 9 of 26

Belajar Swoole - Coroutine Clients (HTTP, TCP, gRPC)

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.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Coroutine HTTP Client

Swoole\Coroutine\Http\Client adalah client HTTP coroutine: tidak memblokir worker saat menunggu respons.

HTTP GET sederhana
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:

  • Konstruktor: new Client($host, $port, $ssl)$ssl = true untuk HTTPS.
  • set() untuk konfigurasi (timeout, header default, dst).
  • Setelah get()/post(), hasil ada di properti: statusCode, body, headers, errCode.
  • Wajib close() setelah selesai untuk mengembalikan koneksi.

Versi singkat memakai helper Coroutine\Http\get() (yang kita pakai di episode 8):

Helper get
$resp = Coroutine\Http\get('https://api.example.com/data');
echo $resp->getBody();

POST dan JSON

HTTP POST JSON
$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']));

Coroutine TCP/UDP Client

Untuk protokol custom atau layanan yang tidak HTTP (game server, device IoT, protokol internal), pakai client TCP/UDP:

TCP client coroutine
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.

Coroutine MySQL dan Redis

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:

Coroutine MySQL & Redis
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 Client

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):

gRPC call via HTTP2 client
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;
});

Aggregator API: Praktik Parallel Fan-Out

Sekarang kita gabungkan semuanya. Kita bangun endpoint /dashboard yang mengambil 4 sumber data (user, orders, stats, notifikasi) — secara parallel:

aggregator.php - parallel upstream calls
<?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.

Common Pitfalls

MasalahPenyebabSolusi
client->body kosongTimeout / koneksi gagalCek statusCode dan errCode
Memory bertambah tiap callclose() tidak dipanggilSelalu close() atau pakai pool (episode 13)
Semua upstream jalan berurutanClient dipakai di luar coroutine / tanpa WaitGroupPastikan setiap call dibungkus coroutine + WaitGroup
Cannot connect to serverPort salah / firewallVerifikasi dengan nc -vz host port

Penutup

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).
  • Client MySQL/Redis coroutine butuh --enable-mysqlnd.
  • Aggregator API: WaitGroup + coroutine membuat N panggilan parallel — latensi = panggilan terlama.
  • Selalu 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 & Processswoole_timer_tick/after, pola cron-like scheduling, dan inter-process communication. Sampai jumpa di episode 10!

Belajar Swoole - Coroutine Clients (HTTP, TCP, gRPC) | Belajar Swoole