Belajar Swoole - HTTP Server Lanjutan: Routing & Middleware
Episode 5 of 26

Belajar Swoole - HTTP Server Lanjutan: Routing & Middleware

Menaikkan level HTTP server Swoole dengan routing statis dan regex, pola dispatcher yang rapi, lalu membangun pipeline middleware untuk CORS, logging, error handling, dan rate limiting tanpa framework.

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

Pendahuluan

Setelah di episode 4 kita membangun HTTP server dasar dengan routing manual yang masih statis — pada episode kali ini kita menaikkan level: routing lanjutan (regex, parameterized routes, dispatcher yang rapi) dan middleware pipeline untuk menangani hal-hal lintas-route seperti CORS, logging, dan error handling.

Mengapa episode ini penting? Karena aplikasi nyata tidak pernah se-sederhana hello world. Pola yang kita bangun di sini — dispatcher + middleware — adalah pola yang sama persis dipakai framework-framework besar (Hyperf, Laravel Octane). Menguasainya secara manual akan membuat kalian memahami cara kerja framework, bukan sekadar pemakainya.

Routing Statis dan Parameterized

Route statis cocok untuk daftar endpoint tetap, tapi aplikasi nyata butuh parameter di path, misal /users/42 atau /posts/2026/08:

Route parameter dengan regex
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
 
$server = new Server('0.0.0.0', 9501);
 
function dispatch(string $path): ?callable
{
    $routes = [
        '/^\/users\/(\d+)$/' => function (Request $req, Response $res, array $m) {
            $res->end("User ID: {$m[1]}");
        },
        '/^\/posts\/(\d{4})\/(\d{2})$/' => function (Request $req, Response $res, array $m) {
            $res->end("Posts {$m[1]}-{$m[2]}");
        },
    ];
 
    foreach ($routes as $pattern => $handler) {
        if (preg_match($pattern, $path, $matches)) {
            array_shift($matches);
            return fn (Request $req, Response $res) => $handler($req, $res, $matches);
        }
    }
 
    return null;
}
 
$server->on('Request', function (Request $req, Response $res) {
    $path = rtrim($req->server['request_uri'] ?? '/', '/');
    $handler = dispatch($path);
 
    if ($handler === null) {
        $res->status(404);
        $res->end('Not Found');
        return;
    }
 
    $handler($req, $res);
});
 
$server->start();

Keuntungan regex routes: pola {id} yang dibatasi digit tidak akan ketuker dengan route lain, dan capture group langsung menjadi parameter — pola yang sama dipakai library routing kelas berat.

Pattern Dispatcher yang Rapi

Dispatcher di atas masih mencampur pencocokan dan eksekusi. Untuk skala lebih besar, pisahkan menjadi tiga bagian:

  1. Route table — daftar pattern => handler (bisa di-cache di memori, episode 4).
  2. Matcher — fungsi yang mencari handler untuk sebuah path (pure function, mudah di-unit test).
  3. Caller — eksekusi handler lengkap dengan 404/405.

Pisahan ini membuat kode mudah dites tanpa harus menyalakan server. Untuk proyek besar, pertimbangkan library router murni (misal nikic/fast-route atau league/route) yang bisa digabung dengan dispatcher kalian — konsepnya sama, tinggal implementasinya dipercayakan ke library.

Middleware: Konsep dan Pipeline

Middleware adalah fungsi yang membungkus handler — berjalan sebelum (memodifikasi request) dan/atau sesudah (memodifikasi response). Dalam Swoole, kita bisa menumpuknya dalam array dan menjalankannya berurutan:

100%

Implementasi sederhana: setiap middleware adalah closure yang menerima $req, $res, dan $next:

Middleware pipeline sederhana
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
 
$middlewares = [
    function (Request $req, Response $res, callable $next) {
        $res->header('Access-Control-Allow-Origin', '*');
        $res->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
        $res->header('Access-Control-Allow-Headers', 'Content-Type');
        if ($req->server['request_method'] === 'OPTIONS') {
            $res->status(204);
            $res->end();
            return;
        }
        $next($req, $res);
    },
    function (Request $req, Response $res, callable $next) {
        $start = microtime(true);
        $next($req, $res);
        $ms = round((microtime(true) - $start) * 1000, 2);
        $res->header('X-Latency', "{$ms}ms");
    },
];
 
function runPipeline(array $middlewares, Request $req, Response $res, callable $handler): void
{
    $pipeline = $handler;
    foreach (array_reverse($middlewares) as $mw) {
        $pipeline = fn (Request $r, Response $s) => $mw($r, $s, $pipeline);
    }
    $pipeline($req, $res);
}
 
$server = new Server('0.0.0.0', 9501);
 
$server->on('Request', function (Request $req, Response $res) {
    $handler = fn (Request $r, Response $s) => $s->end('Hello Middleware!');
    runPipeline($middlewares, $req, $res, $handler);
});
 
$server->start();

Catatan penting dari contoh di atas: array_reverse dipakai karena middleware terakhir di daftar adalah yang paling dekat dengan handler — pola standar di framework (disebut onion model).

Middleware Praktis: CORS, Logging, Error Handling

CORS (Cross-Origin Resource Sharing)

Middleware pertama di pipeline sudah mencontohkan CORS: set header Access-Control-* dan tangani request OPTIONS (preflight) dengan response 204 tanpa meneruskan ke handler.

Logging

Buat log request dengan format terstruktur:

Middleware logging
function (Request $req, Response $res, callable $next) {
    $start = microtime(true);
    $next($req, $res);
    $line = sprintf(
        '%s %s %.2fms',
        $req->server['request_method'] ?? 'GET',
        $req->server['request_uri'] ?? '/',
        (microtime(true) - $start) * 1000
    );
    echo date('c') . " $line\n";
}

Di production, ganti echo dengan logger async (misal monolog + buffer, atau tulis lewat task worker — episode 11).

Error Handling Global

Bungkus handler dalam try/catch di level on('Request'):

Try/catch global
$server->on('Request', function (Request $req, Response $res) {
    try {
        $handler = dispatch($req->server['request_uri']);
        if ($handler === null) {
            $res->status(404);
            $res->end('Not Found');
            return;
        }
        $handler($req, $res);
    } catch (Throwable $e) {
        error_log($e->getMessage());
        $res->status(500);
        $res->end('Internal Server Error');
    }
});

Jangan pernah biarkan exception bocor tanpa response — client akan menggantung menunggu timeout.

Common Pitfalls

  • Header CORS tidak pernah sampai ke browser karena end() dipanggil handler sebelum middleware mengubah response — jalankan middleware sebelum handler, atau atur header setelahnya.
  • Response setelah end() — memanggil method Response setelah end() akan melempar error. Selalu cek status.
  • Regex yang merangkak (catastrophic backtracking) — batasi pola dengan quantifier yang jelas (\d+ bukan .*).
  • Middleware mengubah objek Request — objek Request di Swoole tidak immutable seperti PSR-7; jika kalian mengubah header di middleware, pastikan handler membaca dari objek yang sama.

Penutup

Pada episode 5 ini, kalian telah membangun routing lanjutan dan pipeline middleware.

Inti yang harus dibawa pulang:

  • Regex routes memungkinkan parameter di path dan 404/405 yang presisi.
  • Pisahkan route table, matcher, dan caller agar mudah dites.
  • Middleware = fungsi pembungkus handler; pipeline dengan pola onion (array_reverse).
  • Contoh nyata: CORS, logging berstruktur, dan error handling global.
  • Selalu pastikan setiap request mendapat response tepat satu kali.

Di episode 6 selanjutnya, kita masuk ke dunia realtime: WebSocket server dengan Swoole\WebSocket\Server — koneksi dua arah, event open/message/close, dan broadcast ke banyak client. Sampai jumpa di episode 6!

Belajar Swoole - HTTP Server Lanjutan: Routing & Middleware | Belajar Swoole