D
P
0
← Semua artikel Read in English

WordPress & PHP di Produksi

Semua URL Balas HTTP 200 dengan Body Nol Byte Tanpa Satu Pun Error PHP? `index.php` Menyalakan `$wp_did_header` Sebelum `wp-blog-header.php` Sempat Jalan

· · 6 menit baca
Semua URL Balas HTTP 200 dengan Body Nol Byte Tanpa Satu Pun Error PHP? `index.php` Menyalakan `$wp_did_header` Sebelum `wp-blog-header.php` Sempat Jalan

Nol byte. Bukan halaman error, bukan layar putih dengan pesan, bukan 500. Status barisnya 200 OK, Content-Length nol, dan body-nya benar-benar kosong sampai ke byte terakhir yang tidak pernah datang. Semua URL begitu, halaman depan maupun bagian dalam, dan error log PHP bersih seperti belum ada yang menyentuhnya hari itu.

Situasinya sudah panas sebelum saya masuk. Ada pembersihan berkas di server beberapa jam sebelumnya, dilakukan orang lain, dan setelah itu situsnya kosong. Kesimpulan yang berkeliaran di percakapan sudah terbentuk sendiri, dan saya sempat ikut mempercayainya: pasti ada yang kehapus. Tema, wp-includes, entah apa. Ini kelihatan seperti instalasi yang isinya dipreteli.

Yang tidak cocok dengan cerita itu justru bentuk keluarannya. Instalasi yang temanya hilang tetap mengeluarkan sesuatu, entah halaman wp_die yang bilang direktori temanya tidak ada, atau tema bawaan kalau masih terpasang. Nol byte itu lain sama sekali. Nol byte berarti tidak ada yang pernah dirender.

Shell menjawab hal yang sama, dan itu membuang separuh tersangka

Sebelum menebak lebih jauh, saya jalankan berkas depannya langsung dari shell, di direktori situsnya, tanpa web server sama sekali.

php index.php | wc -c

Hasilnya nol. Persis sama seperti lewat Apache.

Ini satu perintah yang menghapus banyak sekali kemungkinan sekaligus. Kalau body kosong hanya muncul lewat web server tapi tidak muncul di CLI, tersangkanya panjang dan menyebalkan: aturan rewrite, .htaccess, output buffering yang ditelan, plugin cache yang menyajikan berkas kosong, proxy atau CDN di depan, header yang dipotong di tengah jalan. Begitu CLI mengembalikan nol yang sama, semua lapisan itu tidak relevan. PHP-nya sendiri memang tidak menghasilkan apa-apa, dan Apache dengan patuh meneruskan ketiadaan itu apa adanya, lengkap dengan 200 karena memang tidak ada yang gagal.

Jadi masalahnya ada di dalam PHP, di sesuatu yang berhenti sebelum sempat mencetak apa pun, tanpa menganggap dirinya salah.

Memuat WordPress dengan tangan, dan ternyata situsnya hidup

Langkah berikutnya saya lakukan dari direktori yang sama, masih read-only, masih tanpa menyentuh satu berkas pun. Saya coba boot WordPress sendiri, bertahap, mulai dari yang paling dasar.

php -r 'require "/path/ke/docroot/wp-load.php"; echo get_bloginfo("name"), " | ", $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts}");'

Jalan sempurna. Nama situsnya keluar, koneksi database hidup, jumlah post masuk akal. Artinya wp-config.php utuh, kredensialnya benar, wp-settings.php sampai tuntas, plugin ter-load tanpa fatal. Instalasi yang katanya dipreteli itu ternyata baik-baik saja sampai kedalaman yang cukup jauh.

Lalu satu tingkat lebih tinggi, jalur yang sebenarnya dipakai untuk merender halaman:

php -r 'define("WP_USE_THEMES", true); require "/path/ke/docroot/wp-blog-header.php";' | wc -c

175.528.

Seratus tujuh puluh lima ribu byte HTML utuh, dari docroot yang sama, tema yang sama, database yang sama. Saya cek ujungnya dan ada tepat satu </html> di sana, jadi bukan potongan yang kebetulan panjang, melainkan halaman yang selesai dirender sampai habis. Situs yang katanya hancur itu sanggup merender dirinya sendiri secara penuh, detik itu juga, asal dipanggil dengan cara yang benar.

Pada titik ini ruang pencariannya sudah menyusut dari ribuan berkas menjadi satu. Semua yang di belakang berfungsi. Yang tidak berfungsi cuma pintu depannya.

Isi index.php: guard yang membatalkan dirinya sendiri

<?php
if ( ! isset( $wp_did_header ) ) {
    $wp_did_header = true;
    require_once __DIR__ . '/wp-blog-header.php';
}

Dibaca sekilas, ini terlihat seperti versi yang lebih hati-hati dari index.php bawaan. Ada penjagaan supaya bootstrap tidak jalan dua kali, ada require_once yang terdengar lebih aman daripada require polos. Kelihatan seperti hasil orang yang berusaha memperbaiki sesuatu.

Masalahnya, wp-blog-header.php sendiri dibuka dengan syarat yang sama persis. Ini bentuknya di core, saya buang komentar dokumentasinya saja:

<?php
if ( ! isset( $wp_did_header ) ) {
 
    $wp_did_header = true;
 
    require_once __DIR__ . '/wp-load.php';
 
    wp();
 
    require_once ABSPATH . WPINC . '/template-loader.php';
}

Seluruh isi berkas itu ada di dalam guard. Tidak ada satu pun pernyataan di luarnya.

Jadi begini urutannya. index.php memeriksa $wp_did_header, belum ada, masuk. index.php menyetel $wp_did_header = true. index.php me-require wp-blog-header.php. Berkas itu dimuat, dieksekusi dari baris pertama, memeriksa $wp_did_header, dan menemukannya sudah menyala. Kondisinya salah, blok dilewati, berkas selesai. WordPress tidak pernah dimuat sama sekali.

require_once sama sekali tidak menolong di sini, dan sempat menyesatkan saya sebentar. Berkasnya memang benar-benar dimuat dan benar-benar dieksekusi, jadi bukan kasus "sudah pernah di-include lalu dilewati". Yang terjadi jauh lebih sunyi: berkasnya dijalankan dengan patuh, membaca kondisi di barisnya sendiri, dan memutuskan bahwa dirinya tidak punya pekerjaan. Tidak ada yang gagal. PHP tidak punya alasan untuk mengeluh. Skripnya selesai dengan status nol, tidak mencetak apa pun, dan Apache membalas 200 dengan body kosong karena itulah yang sejujurnya dihasilkan.

Ada satu kerusakan kedua yang tersembunyi di balik yang pertama. Versi tulis ulang itu juga kehilangan define( 'WP_USE_THEMES', true ). Konstanta itulah yang memberi tahu jalur bootstrap bahwa request ini harus dirender jadi halaman lewat tema, bukan sekadar memuat WordPress ke memori. Andai guard-nya diperbaiki tapi konstantanya tetap hilang, situsnya tetap kosong, cuma dengan mekanisme yang berbeda, dan babak debugging berikutnya akan dimulai dari nol lagi.

Kembalikan index.php ke dua pernyataan, dan jangan pasang duluan

Bentuk yang benar tidak punya penjagaan apa pun, dan memang tidak butuh:

<?php
define( 'WP_USE_THEMES', true );
 
require __DIR__ . '/wp-blog-header.php';

Itu saja. Penjagaan dobel-eksekusi bukan tugas index.php, dan tidak pernah jadi tugasnya.

Yang lebih penting daripada isi tambalannya adalah kapan tambalan itu dipasang. Di kasus ini ada perubahan konfigurasi server lain yang juga menunggu diperbaiki, yaitu docroot yang menunjuk ke direktori yang salah. Godaannya besar untuk membetulkan yang itu duluan karena kelihatannya lebih besar dan lebih menjelaskan. Tapi selama index.php di docroot yang benar masih membatalkan dirinya sendiri, mengembalikan docroot lebih dulu justru memindahkan situsnya dari menyajikan konten lama menjadi menyajikan halaman kosong total. Buruk berubah jadi lebih buruk, di depan mata semua orang yang sedang menonton.

Jadi bootstrap-nya dulu, baru docroot-nya. Dan sebelum keduanya, boot manual yang read-only itu dulu, yang cuma menghitung byte dan tidak mengubah apa pun. Angka 175.528 itu bukan sekadar petunjuk teknis. Itu yang membuat saya bisa mengatakan dengan yakin bahwa tidak ada yang hilang, sebelum menyentuh apa pun, di saat semua orang sudah siap percaya bahwa berbulan-bulan pekerjaan lenyap.

Sentinel itu milik berkas yang dipanggil, bukan milik pemanggilnya

Aturan yang saya bawa keluar dari kasus ini cukup pendek. Jangan pernah menyetel sendiri variabel sentinel yang dimiliki berkas yang akan kamu panggil. Sentinel semacam itu dipasang OLEH berkas tujuan, di dalam dirinya sendiri, sebagai catatan bahwa dia sudah pernah jalan. Menyalakannya lebih dulu dari luar bukan tindakan hati-hati, melainkan berbohong kepada berkas itu bahwa dia sudah selesai bekerja. Dan berkas yang percaya dirinya sudah selesai bekerja tidak akan protes. Dia akan langsung pulang.

Sisanya soal membaca gejala. Kombinasi 200, body nol byte, error log bersih, dan hasil yang identik di CLI bukan tanda tema rusak atau plugin bentrok. Itu sidik jari bootstrap yang berhenti sebelum baris pertamanya yang berguna. Sekarang, kalau ada halaman yang kosong tanpa mengeluh sedikit pun, hal pertama yang saya lakukan bukan membuka functions.php, tapi menjalankan berkas depannya dari shell dan menghitung byte-nya.