Belajar FrankenPHP - HTTP/3, QUIC & WebSocket
Episode 18 of 25

Belajar FrankenPHP - HTTP/3, QUIC & WebSocket

Memanfaatkan transportasi web modern di FrankenPHP: HTTP/3 dan QUIC lewat port 443 UDP, Early Hints (status 103) untuk mempercepat load halaman, serta real-time communication menggunakan Mercure hub untuk SSE dan event broadcast ke banyak klien.

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

Pendahuluan

Setelah episode 17 mengukur performa, episode ini mendorong protokol ke batas modern. FrankenPHP, berkat fondasi Caddy, menawarkan tiga teknologi transport yang banyak web server belum dukung penuh: HTTP/3 (berbasis QUIC di atas UDP), Early Hints (status 103), dan komunikasi real-time via Mercure. Ketiganya bekerja tanpa satu baris pun kode aplikasi — cukup konfigurasi.

Mengapa penting? HTTP/3 mengurangi latency di jaringan yang buruk (pindah jaringan, satelit, mobile); Early Hints memangkas waktu render halaman dengan mem-bootstrap resource penting lebih awal; Mercure membuka pintu ke fitur real-time (notifikasi, live updates) tanpa memelihara infrastruktur WebSocket sendiri.

HTTP/3 & QUIC: Lebih Cepat di Jaringan Buruk

HTTP/3 adalah HTTP di atas QUIC, protokol yang berjalan di UDP (bukan TCP). Manfaatnya:

  • 0-RTT — koneksi kembali bisa langsung kirim data (tanpa handshake penuh).
  • Tanpa head-of-line blocking — kehilangan satu paket tidak menghentikan semua stream (masalah klasik TCP).
  • Handover lebih halus — koneksi bertahan saat IP berubah (WiFi → mobile).

Caddy mengaktifkan HTTP/3 secara otomatis ketika port 443 UDP terbuka. Artinya di FrankenPHP, HTTP/3 sudah aktif tanpa konfigurasi — asalkan firewall membuka UDP:

Buka port UDP 443
sudo ufw allow 443/udp
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp

Di Docker, ekspos port UDP-nya:

Docker expose UDP 443
docker run \
  -p 443:443 \
  -p 443:443/udp \
  dunglas/frankenphp

Tip

Pemeriksaan: di Chrome DevTools → Network, protokol request ditampilkan sebagai h3. Di curl modern: curl --http3 https://domain. Jika belum h3, berarti port UDP 443 belum terbuka — periksa firewall/nat.

Early Hints (Status 103)

Status 103 Early Hints mengirim respons sementara sebelum respons penuh, berisi resource yang dibutuhkan lebih awal (preload). Browser langsung mulai mengunduh — tanpa menunggu HTML selesai diproses. Dampak: waktu first contentful paint membaik signifikan, terutama di halaman yang bergantung pada CSS/JS besar.

Di FrankenPHP, Early Hints tersedia di PHP:

Kirim Early Hints dari PHP
<?php
if (function_exists('frankenphp_early_hints')) {
    frankenphp_early_hints([
        'Link: </static/app.css>; rel=preload; as=style',
        'Link: </static/app.js>; rel=preload; as=script',
    ]);
}

Caddy juga mendukung Early Hints pada respons statis via directive header dengan status 103:

Caddyfile - Early Hints statis
localhost {
    root * public/
 
    header /static/* 103 Link "</static/app.css>; rel=preload; as=style"
 
    file_server
}

Urutan kerja 103: browser dapat respons 103 → mulai preload → terima respons 200 → render lebih cepat. Ini adalah "gratis" yang paling murah — beberapa baris konfigurasi, efek terukur.

WebSocket Native

Caddy mendukung WebSocket secara native melalui reverse_proxy — tanpa plugin, tanpa config khusus. FrankenPHP meneruskannya secara otomatis:

Caddyfile - WebSocket passthrough
localhost {
    @ws path /ws
    reverse_proxy @ws ws://backend:8080
 
    root * public/
    php_server
}

Upgrade header diteruskan, koneksi dipertahankan, dan biaya koneksi idle mendekati nol. Untuk aplikasi PHP murni yang butuh WebSocket server sendiri, panduan resmi merekomendasikan menjalankan server WebSocket terpisah dan meneruskan lewat reverse_proxy di atas.

Mercure: Real-time untuk Aplikasi PHP

Mercure adalah hub komunikasi real-time bawaan FrankenPHP — dirancang sebagai alternatif modern untuk WebSocket yang tetap kompatibel dengan HTTP/SSE. Alur kerjanya:

  1. Klien berlangganan (SSE) ke hub: https://domain/.well-known/mercure?topic=/bookings.
  2. Server mempublikasikan event (update) ke hub.
  3. Hub meneruskan event ke semua subscriber topik itu — dalam milidetik.

Aktifkan hub Mercure:

Caddyfile - Mercure hub
{
    mercure {
        publisher_jwt "ZXhhbXBsZQ=="
        anonymous
        cors_origins https://kaliansite.com
    }
}
 
kaliansite.com {
    root * public/
    php_server
}

Publikasikan event dari PHP — tanpa WebSocket server khusus:

Publish event Mercure
<?php
$hub = 'https://kaliansite.com/.well-known/mercure';
$token = 'publisher_jwt_anda';
 
frankenphp_mercure_publish(
    $hub,
    '/bookings/new',
    json_encode(['id' => 42, 'status' => 'confirmed']),
    null,
    null,
    ['Content-Type' => 'application/json'],
    $token,
);

frankenphp_mercure_publish() adalah helper bawaan FrankenPHP untuk mempublikasikan event dari PHP. Di sisi klien, cukup EventSource:

Subscribe di frontend
const es = new EventSource(
  'https://kaliansite.com/.well-known/mercure?topic=/bookings/new',
);
 
es.addEventListener('message', (event) => {
  const booking = JSON.parse(event.data);
  renderBooking(booking); // update UI real-time
});

Note

Mercure bekerja di atas SSE (Server-Sent Events) — satu arah (server → klien), yang mencakup mayoritas kasus (notifikasi, live feed, dashboard). Untuk komunikasi dua arah penuh, gunakan WebSocket native lewat reverse_proxy di atas. Pilih berdasarkan kebutuhan, bukan tren.

Komposisi Lengkap dalam Satu Konfigurasi

Caddyfile - transport modern lengkap
{
    mercure {
        publisher_jwt "ZXhhbXBsZQ=="
        anonymous
        cors_origins https://kaliansite.com
    }
}
 
kaliansite.com {
    root * public/
    encode zstd br gzip
 
    php_server {
        # 103 Early Hints via Caddy (opsional)
    }
 
    @ws path /ws
    reverse_proxy @ws ws://backend:8080
}

Dengan port 443 UDP terbuka, stack ini menyajikan: HTTP/3, HTTP/2, HTTP/1.1, Early Hints, SSE (Mercure), dan WebSocket — semuanya dari satu binary.

Common Pitfalls

  • UDP 443 tertutup: browser tetap jalan (fallback HTTP/2) tapi kalian kehilangan semua manfaat HTTP/3. Periksa dengan curl --http3.
  • encode dan SSE: kompresi tidak boleh menambah buffering berlebihan pada stream SSE. Jika event terasa tertunda, nonaktifkan kompresi untuk path Mercure.
  • JWT Mercure lemah: publisher_jwt adalah rahasia — jangan pernah taruh di Caddyfile yang masuk git; injeksi via env var (episode 11).
  • CORS salah: klien di domain lain gagal subscribe. Set cors_origins dengan benar.

Penutup

Pada episode 18 ini, kalian telah memanfaatkan transportasi web modern di FrankenPHP.

Inti yang harus dibawa pulang:

  • HTTP/3 aktif otomatis saat UDP 443 terbuka; verifikasi dengan h3.
  • Early Hints 103 mempreload resource penting → render lebih cepat (sedikit konfigurasi, efek besar).
  • WebSocket via reverse_proxy ws:// — tanpa plugin.
  • Mercure untuk real-time berbasis SSE: publish dari PHP dengan frankenphp_mercure_publish(), subscribe di frontend dengan EventSource.
  • Pilih WebSocket vs Mercure sesuai arah komunikasi yang dibutuhkan.

Di episode 19 selanjutnya kita pindah ke deployment skala: Docker & Kubernetes — membangun image production yang benar, volume caddy_data, non-root, dan deploy ke Kubernetes dengan ingress + HPA. Sampai jumpa di episode 19!

Belajar FrankenPHP - HTTP/3, QUIC & WebSocket | Belajar FrankenPHP