Membangun HTTP server pertama dengan Swoole\Http\Server, memahami objek Request dan Response, siklus event on Request, routing manual tanpa framework, pengaturan port dan host, serta perbedaan lifecycle dengan PHP-FPM yang terlihat dari perilaku state di dalam worker.

Setelah di episode 3 kita berhasil menginstall dan memverifikasi Swoole, pada episode ini kita membangun fondasi paling nyata: HTTP server dengan Swoole\Http\Server. Ini adalah jenis server yang paling sering dipakai di dunia nyata — setiap API, setiap aplikasi web long-running, berawal dari sini.
Mengapa episode ini penting? Karena inilah momen kalian merasakan perbedaan PHP-FPM secara langsung: kode handler yang sama dieksekusi ribuan kali di dalam proses yang sama, state bertahan antar request, dan koneksi TCP dikelola oleh event loop. Kesalahan yang kalian lakukan di sini akan menghantui seluruh series, jadi mari kita bangun dengan benar.
Kode paling sederhana untuk memulai:
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
$server = new Server('0.0.0.0', 9501);
$server->on('Request', function (Request $req, Response $res) {
$res->header('Content-Type', 'text/plain');
$res->end('Hello from Swoole!');
});
$server->start();Jalankan dan uji:
php server.phpcurl http://127.0.0.1:9501/on('Request') adalah jantung HTTP server: dipanggil untuk setiap request HTTP yang masuk, menerima objek Swoole\Http\Request (permintaan) dan Swoole\Http\Response (jawaban). $res->end() menutup response — wajib dipanggil tepat sekali per request.
Objek Request memuat seluruh informasi HTTP yang kalian kenal dari $_GET, $_POST, dan $_SERVER:
| Properti | Setara PHP-FPM | Isi |
|---|---|---|
$req->get | $_GET | Parameter query string |
$req->post | $_POST | Body form-urlencoded |
$req->server | $_SERVER | Info server (request_method, request_uri, remote_addr) |
$req->header | $_SERVER['HTTP_*'] | Header request |
$req->cookie | $_COOKIE | Cookie |
$req->getContent() | file_get_contents('php://input') | Raw body |
Contoh membaca data request:
$server->on('Request', function (Request $req, Response $res) {
$name = $req->get['name'] ?? 'dunia';
$ua = $req->header['user-agent'] ?? 'unknown';
$res->end("Halo $name! User-Agent: $ua");
});Note
Dalam coroutine context, memanggil $_GET/$_POST global di aplikasi Swoole adalah anti-pattern. Selalu ambil data dari objek Request. Framework seperti Hyperf melakukan ini secara konsisten — kita lihat di episode 15.
Objek Response memberi kontrol penuh atas output:
$server->on('Request', function (Request $req, Response $res) {
$res->status(200);
$res->header('Content-Type', 'application/json');
$res->header('X-Powered-By', 'Swoole');
$res->end(json_encode(['status' => 'ok']));
});Metode penting Response:
$res->status($code) — set HTTP status code.$res->header($k, $v) — set response header.$res->cookie($k, $v, $expire) — set cookie.$res->write($chunk) — tulis sebagian response (untuk streaming / chunked).$res->end($data) — selesaikan response.$res->redirect($url, $code) — redirect HTTP (episode 18).Tanpa framework, routing adalah tanggung jawab kita sendiri. Pola paling bersih: parse request_uri, lalu dispatch ke handler:
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
$routes = [
'/' => fn (Request $req, Response $res) => $res->end('Home'),
'/health' => fn (Request $req, Response $res) => $res->end('OK'),
'/api/users' => fn (Request $req, Response $res) => $res->end('[{"id":1}]'),
];
$server = new Server('0.0.0.0', 9501);
$server->on('Request', function (Request $req, Response $res) use ($routes) {
$path = rtrim($req->server['request_uri'] ?? '/', '/');
$path = $path === '' ? '/' : $path;
if (isset($routes[$path])) {
$routes[$path]($req, $res);
} else {
$res->status(404);
$res->end('Not Found');
}
});
$server->start();Perhatikan request_method juga perlu dicek bila ingin membedakan GET/POST untuk path yang sama — detail ini kita naikkan levelnya di episode 5 dengan regex routes dan middleware.
Inilah perbedaan paling terasa dengan PHP-FPM. Variabel di luar handler hidup terus selama worker hidup:
$server = new Server('0.0.0.0', 9501);
$hits = 0; // hidup selama worker hidup
$server->on('Request', function (Request $req, Response $res) use (&$hits) {
$hits++;
$res->end("Hits: $hits");
});Jalankan dan refresh berulang kali — counter terus naik. Di PHP-FPM counter akan selalu 1. Tapi ingat pelajaran episode 2: $hits hanya bertahan per worker. Dengan banyak worker, angka yang kalian lihat bergantung worker mana yang menangani request. State lintas worker memakai Swoole\Table/Atomic (episode 12).
Konstruktor server bisa menerima beberapa konfigurasi:
$server = new Server('0.0.0.0', 9501, SWOOLE_PROCESS);
$server->set([
'worker_num' => 4,
'max_request' => 10000,
'log_file' => '/tmp/swoole.log',
'dispatch_mode' => 2,
]);| Opsi | Arti |
|---|---|
worker_num | Jumlah worker proses |
max_request | Request maksimal per worker sebelum direcycle |
log_file | Lokasi log server |
dispatch_mode | Cara distribusi request ke worker (detail episode 20) |
end() dua kali menyebabkan error. Pastikan satu jalur eksekusi per request.start(): server tidak akan berjalan tanpa memanggil start() di akhir.curl timeout padahal server jalan? Cek port dengan ss -tlnp.php server.php berjalan, bukan browser.Pada episode 4 ini, kalian telah membangun HTTP server pertama dan memahami fondasinya.
Inti yang harus dibawa pulang:
Swoole\Http\Server + on('Request') adalah pola dasar HTTP di Swoole.Request, output dikontrol objek Response.request_uri, dispatch ke handler, 404 untuk yang tidak cocok.worker_num, max_request, dispatch_mode, log_file.Di episode 5 selanjutnya, kita menaikkan level HTTP server: routing lanjutan dengan static & regex routes serta middleware — CORS, logging, dan error handling yang rapi. Sampai jumpa di episode 5!