Teks adalah penghuni utama web — HTML, JSON, CSS, JavaScript — dan teks sangat murah dikompresi: halaman 6 KB bisa mengecil menjadi ratusan byte. Untuk pengguna di jaringan lambat, selisih itu adalah perbedaan antara halaman yang terasa cepat dan yang menunggu; untuk aplikasi bertrafik besar, selisih itu adalah tagihan bandwidth yang berlipat. HTTP menyediakan mekanisme lengkap untuknya: klien mengumumkan kemampuannya lewat Accept-Encoding, server memilih encoding dan menandainya lewat Content-Encoding, dan Vary menjaga cache agar tidak mencampur versi terkompresi dengan versi polos. Chapter penutup section ini membahas kapan dan bagaimana mengompresi — dari perhitungan sederhana dengan gzencode(), negosiasi encoding, sampai alasan kuat menyerahkan pekerjaan ini ke server web.

Mengapa Kompresi Menghemat Bandwidth #

Mari kita ukur dulu klaimnya. gzencode() menghasilkan byte gzip siap kirim; membandingkan panjangnya dengan byte asli memberi gambaran nyata:

php
<?php
$artikel = str_repeat("PHP adalah bahasa skrip serbaguna untuk pengembangan web modern. ", 100);

$ukuranAsli = strlen($artikel);
$gzip = gzencode($artikel, 6);

printf("ukuran asli    : %d byte\n", $ukuranAsli);
printf("setelah gzip   : %d byte\n", strlen($gzip));
printf("rasio kompresi : %.1f%% dari ukuran asli\n", strlen($gzip) / $ukuranAsli * 100);

Output yang Dihasilkan:

text
ukuran asli    : 6500 byte
setelah gzip   : 113 byte
rasio kompresi : 1.7% dari ukuran asli

Teks 6500 byte menyusut menjadi 113 byte — 1,7% dari ukuran aslinya. Angka ini memang kasar karena contohnya sangat repetitif; halaman HTML nyata biasanya mendarat di kisaran 20–30%. Meski begitu, arah kesimpulannya tetap: kompresi gzip adalah penghematan bandwidth termurah yang bisa Anda aktifkan tanpa mengubah satu baris logika aplikasi. Angka 6 pada argumen kedua adalah level kompresi (1–9): makin tinggi makin kecil hasilnya, dengan waktu proses yang naik — level 6 adalah titik seimbang yang dipakai luas.

Kapan Tidak Mengompresi #

Kompresi bukan kemenangan tanpa syarat. Dua kasus berikut justru merugikan:

php
<?php
$payloadKecil = '{"status":"ok","stok":12}';

printf("payload JSON kecil : %d byte\n", strlen($payloadKecil));
printf("versi gzip         : %d byte (justru lebih besar)\n", strlen(gzencode($payloadKecil, 6)));

Output yang Dihasilkan:

text
payload JSON kecil : 25 byte
versi gzip         : 42 byte (justru lebih besar)

Payload 25 byte membengkak menjadi 42 byte setelah digzip — format gzip menambahkan header dan trailer tetap, dan payload sesempit itu tidak punya ruang untuk menyusut. Aturan praktisnya: jangan kompres respons di bawah sekitar 1 KB, dan jangan kompres format yang sudah terkompresi sendiri — JPEG, PNG, WebP, MP4, ZIP. CPU yang terbakar untuk mengompresi berkas yang tidak bisa lebih kecil adalah pemborosan murni; konfigurasi server yang baik selalu membatasi kompresi berdasarkan Content-Type dan ukuran minimum.

Negosiasi lewat Accept-Encoding #

Server tidak boleh menebak — klien yang tidak paham gzip akan menerima sampah byte bila tetap dikirimi versi terkompresi. Karena itu setiap permintaan modern membawa header Accept-Encoding berisi daftar encoding yang didukung, dan tugas server memilih satu:

php
<?php
function pilihEncoding(?string $acceptEncoding): string
{
    $didukung = array_map(
        fn (string $bagian): string => strtolower(trim(explode(";", $bagian, 2)[0])),
        explode(",", (string) $acceptEncoding)
    );

    if (in_array("br", $didukung, true)) {
        return "br";
    }
    if (in_array("gzip", $didukung, true)) {
        return "gzip";
    }

    return "identity";
}

foreach (["gzip, deflate, br, zstd", "gzip, deflate", "deflate", null] as $ae) {
    printf("Accept-Encoding: %-24s => %s\n", var_export($ae, true), pilihEncoding($ae));
}

Output yang Dihasilkan:

text
Accept-Encoding: 'gzip, deflate, br, zstd' => br
Accept-Encoding: 'gzip, deflate'          => gzip
Accept-Encoding: 'deflate'                => identity
Accept-Encoding: NULL                     => identity

Parsingnya sederhana namun tahan benturan: daftar dipecah per koma, setiap item dibersihkan dari parameter seperti ;q=0.8 (pemotongan explode(..., 2) menjaga bagian parameter ikut terbuang), lalu dinormalkan ke huruf kecil. Pemilihan memprioritaskan br (Brotli — rasio lebih baik dari gzip) sebelum gzip, dan jatuh ke identity berarti mengirim tanpa kompresi; nilai null — klien yang tidak mengirim header sama sekali — juga berakhir di sana.

Respons Terkompresi Manual dengan gzencode() #

Seluruh bahan sudah ada; tinggal merangkainya menjadi pembangun respons lengkap yang memutuskan sendiri apakah kompresi layak:

php
<?php
function pilihEncoding(?string $acceptEncoding): string
{
    $didukung = array_map(
        fn (string $bagian): string => strtolower(trim(explode(";", $bagian, 2)[0])),
        explode(",", (string) $acceptEncoding)
    );

    if (in_array("br", $didukung, true)) {
        return "br";
    }
    if (in_array("gzip", $didukung, true)) {
        return "gzip";
    }

    return "identity";
}

function responsTerkompresi(string $body, string $acceptEncoding): array
{
    $encoding = pilihEncoding($acceptEncoding);

    $headers = ["Content-Type: text/html; charset=UTF-8"];
    if ($encoding === "gzip" && strlen($body) >= 1024) {
        $body = gzencode($body, 6);
        $headers[] = "Content-Encoding: gzip";
    }

    $headers[] = "Vary: Accept-Encoding";
    return [200, $headers, $body];
}

$halaman = str_repeat("<p>Selamat datang di Toko Budi</p>", 100);

[$status, $headers, $body] = responsTerkompresi($halaman, "gzip, deflate");
echo "klien gzip  : status $status, body " . strlen($body) . " byte\n";
foreach ($headers as $h) {
    echo "  $h\n";
}

[$status, $headers, $body] = responsTerkompresi($halaman, "deflate");
echo "klien polos : status $status, body " . strlen($body) . " byte\n";
foreach ($headers as $h) {
    echo "  $h\n";
}

Output yang Dihasilkan:

text
klien gzip  : status 200, body 84 byte
  Content-Type: text/html; charset=UTF-8
  Content-Encoding: gzip
  Vary: Accept-Encoding
klien polos : status 200, body 3400 byte
  Content-Type: text/html; charset=UTF-8
  Vary: Accept-Encoding

Ada tiga keputusan penting dalam fungsi tersebut:

  1. Kompresi hanya bila dua syarat terpenuhi: klien mendukung gzip ($encoding === "gzip") dan body minimal 1 KB — gabungan dua aturan “kapan tidak mengompresi” dari bagian sebelumnya.
  2. Content-Encoding: gzip wajib ditempelkan setiap kali body dikompresi; tanpa header ini, klien menampilkan byte gzip mentah sebagai teks rusak.
  3. Vary: Accept-Encoding dikirim selalu, bahkan saat respons tidak dikompresi.

Mengapa Vary begitu penting? Bayangkan proxy menyimpan versi gzip dari sebuah URL karena permintaan pertama datang dari browser modern. Permintaan berikutnya dari klien tanpa gzip kini dilayani dari salinan itu — dan klien tersebut menerima byte gzip yang tidak bisa dibaca. Header Vary: Accept-Encoding memberi tahu semua cache di jalan bahwa salinan hanya boleh dipakai bila nilai Accept-Encoding pada permintaan berikutnya sama dengan saat disimpan; dengan begitu versi gzip dan versi polos disimpan sebagai dua entri terpisah. Ini adalah kelanjutan langsung konsep Vary pada chapter Cache-Control , dan alasan yang sama menjelaskan mengapa ETag versi terkompresi harus berbeda dari versi polos di chapter ETag .

ob_gzhandler dan Kompresi di Level Server #

PHP klasik punya jalan pintas: menyerahkan kompresi pada output buffering lewat callback ob_gzhandler, yang memampatkan seluruh output secara otomatis:

php
<?php
// kompresi-legacy.php — dari folder berkas ini, jalankan:
//   php -S 127.0.0.1:8087
// lalu bandingkan keduanya:
//   curl -i -H 'Accept-Encoding: gzip' http://127.0.0.1:8087/kompresi-legacy.php
//   curl -i http://127.0.0.1:8087/kompresi-legacy.php
ob_start("ob_gzhandler");

echo str_repeat("Kompresi otomatis lewat output buffering. ", 200);

Saat diminta dengan Accept-Encoding: gzip, skrip ini dijawab HTTP/1.1 200 OK dengan Content-Encoding: gzip — PHP bahkan ikut memasang Vary: Accept-Encoding secara otomatis — dan body terpampat menjadi sekitar 100 byte dari 8400 byte aslinya. Tanpa header tersebut, callback mengirim output apa adanya. Pola ini tetap berfungsi, tetapi jarang jadi pilihan terbaik: kompresi berjalan di proses PHP (memakan CPU yang sama dengan aplikasi), tidak menjangkau berkas statis, dan tidak mengenal Brotli. Di server produksi, pekerjaan ini seharusnya diserahkan ke web server yang mengompresi sekali lalu menyimpan hasilnya:

ini
gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_vary on;

Konfigurasi nginx di atas memuat dua aturan “apa yang tidak boleh dikompresi” dari bagian sebelumnya dalam bentuk konfigurasi: gzip_min_length 1024 menolak payload kecil, dan gzip_types membatasi kompresi hanya pada tipe teks. Direktif gzip_vary on menempelkan header Vary: Accept-Encoding pada respons yang memenuhi syarat — persis fungsi Vary manual kita. Apache menawarkan kemampuan yang sama lewat mod_deflate dengan pemilihan berdasarkan Content-Type:

ini
AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json

Untuk Brotli, nginx dan Apache memilikinya sebagai modul terpisah; PHP sendiri tidak menyediakan encoding Brotli bawaan, sehingga pemilih br pada fungsi negosiasi kita memang diakhiri sebagai identity di pembangun respons.

Ringkasan #

Kompresi gzip memangkas teks hingga seperempat ukurannya atau lebih, dengan dua pengecualian tegas: payload kecil yang justru membengkak dan format yang sudah terkompresi. Mekanismenya berjalan atas tiga header: Accept-Encoding dari klien sebagai penawaran, Content-Encoding dari server sebagai keputusan, dan Vary: Accept-Encoding sebagai penjaga agar cache tidak mencampur versi. Di PHP, gzencode() plus fungsi negosiasi memberi kendali penuh; di produksi, nginx atau Apache dengan konfigurasi tipe dan ukuran minimum hampir selalu menjadi tempat yang lebih tepat. Dengan respons yang kini terkompresi, kita menutup section ini — pengendalian HTTP response telah lengkap dari header, status, cookie, cache, sampai transfer, dan siap dilanjutkan ke strategi caching di level aplikasi pada section caching dan performance .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh.php

Unduh berkas
php
<?php
// contoh.php — ringkasan kompresi respons: ukuran sebelum-sesudah,
// negosiasi Accept-Encoding, dan pembangun respons terkompresi.

function pilihEncoding(?string $acceptEncoding): string
{
    $didukung = array_map(
        fn (string $bagian): string => strtolower(trim(explode(";", $bagian, 2)[0])),
        explode(",", (string) $acceptEncoding)
    );

    if (in_array("br", $didukung, true)) {
        return "br";
    }
    if (in_array("gzip", $didukung, true)) {
        return "gzip";
    }

    return "identity";
}

function responsTerkompresi(string $body, string $acceptEncoding): array
{
    $encoding = pilihEncoding($acceptEncoding);

    $headers = ["Content-Type: text/html; charset=UTF-8"];
    if ($encoding === "gzip" && strlen($body) >= 1024) {
        $body = gzencode($body, 6);
        $headers[] = "Content-Encoding: gzip";
    }

    $headers[] = "Vary: Accept-Encoding";
    return [200, $headers, $body];
}

echo "=== Rasio kompresi teks repetitif ===\n";
$artikel = str_repeat("PHP adalah bahasa skrip serbaguna untuk pengembangan web modern. ", 100);
$ukuranAsli = strlen($artikel);
$gzip = gzencode($artikel, 6);
printf("ukuran asli    : %d byte\n", $ukuranAsli);
printf("setelah gzip   : %d byte\n", strlen($gzip));
printf("rasio kompresi : %.1f%% dari ukuran asli\n", strlen($gzip) / $ukuranAsli * 100);

echo "=== Payload kecil tidak layak dikompresi ===\n";
$payloadKecil = '{"status":"ok","stok":12}';
printf("payload JSON kecil : %d byte\n", strlen($payloadKecil));
printf("versi gzip         : %d byte (justru lebih besar)\n", strlen(gzencode($payloadKecil, 6)));

echo "=== Negosiasi Accept-Encoding ===\n";
foreach (["gzip, deflate, br, zstd", "gzip, deflate", "deflate", null] as $ae) {
    printf("Accept-Encoding: %-24s => %s\n", var_export($ae, true), pilihEncoding($ae));
}

echo "=== Respons terkompresi vs polos ===\n";
$halaman = str_repeat("<p>Selamat datang di Toko Budi</p>", 100);

[$status, $headers, $body] = responsTerkompresi($halaman, "gzip, deflate");
echo "klien gzip  : status $status, body " . strlen($body) . " byte\n";
foreach ($headers as $h) {
    echo "  $h\n";
}

[$status, $headers, $body] = responsTerkompresi($halaman, "deflate");
echo "klien polos : status $status, body " . strlen($body) . " byte\n";
foreach ($headers as $h) {
    echo "  $h\n";
}