HTTP Range Request untuk Download File - Partial Content Adalah Protokol, Bukan Sekadar Potongan String
Sebuah download besar terhenti di tengah jalan. File lokalnya masih ada, jadi meminta hanya byte yang belum diterima terdengar masuk akal. Namun, bagaimana jika file di balik URL berubah saat koneksi terputus? Menambahkan bagian kedua file baru ke bagian pertama file lama akan menghasilkan sesuatu yang bukan merupakan versi mana pun.
Itulah cara yang berguna untuk memahami HTTP range request: bukan sebagai jalan pintas untuk memotong file, melainkan sebagai protokol kecil untuk mengirim sebagian dari sebuah representasi tertentu. Perhitungan byte memang penting, tetapi identitas representasi, status respons, dan validator sama pentingnya.
Artikel ini mengikuti RFC 9110, spesifikasi semantik HTTP yang berlaku saat ini. Fokusnya adalah download melalui GET. Resume upload, PUT parsial, API object storage, dan perilaku khusus vendor CDN merupakan bahasan terpisah.
Range mengacu pada representasi, bukan file abstrak
Sebuah URL mengidentifikasi resource, sedangkan respons HTTP membawa representasi terpilih dari resource tersebut. Perbedaannya terlihat ketika content negotiation atau content coding terlibat. Server mungkin memilih bahasa, media type, atau bentuk terenkode tertentu untuk sebuah request. Byte range kemudian mengacu pada rangkaian oktet dari representasi terpilih itu.
RFC 9110 mendefinisikan penanganan range untuk GET. Server diperbolehkan mengabaikan header Range, jadi header tersebut adalah permintaan, bukan instruksi yang dapat dipaksakan client. Meski demikian, dukungannya berguna untuk memulihkan transfer yang terputus dan mengambil hanya sebagian dari representasi besar.
Unit yang umum adalah bytes. Offset dimulai dari nol, dan kedua batasnya bersifat inklusif:
Range: bytes=0-999
Range: bytes=1000-
Range: bytes=-500
Bentuk pertama meminta 1.000 byte, bukan 999. Bentuk kedua dimulai dari offset byte 1.000 lalu berlanjut hingga akhir. Bentuk ketiga meminta maksimal 500 byte terakhir. Jika sebuah suffix lebih panjang daripada representasinya, range terpilih menjadi seluruh representasi. Jika posisi akhir eksplisit melewati panjang saat ini, server menafsirkannya sebagai sisa representasi, bukan membaca melampaui akhir.
Ada satu batas lain yang tidak langsung terlihat. Jika content coding seperti gzip diterapkan, offset mengacu pada rangkaian byte yang sudah terenkode, bukan pada byte yang diperoleh setelah decoding. Karena itu, client sebaiknya memperlakukan metadata respons sebagai acuan alih-alih menghitung offset dari salinan dengan encoding berbeda.
200, 206, dan 416 menceritakan hasil yang berbeda
Tiga status code membentuk alur keputusan praktis. Ketiganya tidak dapat saling dipertukarkan:
200 OKberarti respons membawa representasi terpilih secara lengkap. Server dapat mengembalikannya karena mengabaikan range, tidak mendukung range untuk resource tersebut, atau menilai respons lengkap lebih sesuai.206 Partial Contentberarti server memenuhi request dengan satu atau beberapa bagian dari representasi terpilih.416 Range Not Satisfiableberarti kumpulan range ditolak karena tidak ada range yang dapat dipenuhi atau karena kumpulan range kecil atau tumpang tindih yang berlebihan dianggap sebagai penyalahgunaan.
Untuk 206 dengan satu bagian, respons memerlukan Content-Range yang menempatkan body tersebut di dalam representasi lengkap. Request untuk 1.000 byte pertama dari representasi sepanjang 10.000 byte dapat menghasilkan:
HTTP/1.1 206 Partial Content
Content-Length: 1000
Content-Range: bytes 0-999/10000
[1000 response-body bytes]
Di sini, Content-Length menjelaskan body dalam pesan ini. Nilai itu tidak menyatakan bahwa representasi lengkap hanya sepanjang 1.000 byte. Penyebut dalam Content-Range memberikan panjang lengkapnya.
Jika client meminta bytes=12000- dari representasi sepanjang 10.000 byte tersebut, posisi pertamanya tidak bertumpang tindih dengan representasi. RFC 9110 menyatakan bahwa server yang menghasilkan 416 untuk byte-range request sebaiknya melaporkan panjang lengkap saat ini:
HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */10000
Kata sebaiknya penting di sini. Server tetap bebas mengabaikan Range dan mengembalikan 200 lengkap. Client download harus memeriksa status dan header respons sebelum memutuskan untuk menambahkan, mengganti, atau menolak body yang diterima. Menambahkan setiap respons begitu saja ke file lokal parsial adalah bug korupsi yang hanya menunggu gangguan yang tepat.
Accept-Ranges mengiklankan, tetapi tidak menjanjikan
Sebuah respons dapat menyertakan Accept-Ranges: bytes untuk mengiklankan dukungan byte range bagi target resource itu. Respons juga dapat memakai Accept-Ranges: none untuk memberi tahu bahwa tidak ada range unit yang didukung. Field ini berguna bagi client saat menentukan apakah tindakan resume perlu ditampilkan.
Namun, field tersebut bukan janji yang bertahan selamanya. Spesifikasi secara eksplisit memperingatkan client agar tidak menganggap request berikutnya pasti menerima respons parsial hanya karena respons sebelumnya mengiklankan dukungan. Representasi, kondisi server, atau intermediary dapat berubah. Sebaliknya, client boleh mencoba range meski belum melihat Accept-Ranges.
Request HEAD merupakan cara praktis untuk memeriksa metadata tanpa mentransfer body respons normal. Panduan range request MDN menunjukkan pendekatan ini, tetapi respons GET berikutnya tetap menjadi penentu apa yang benar-benar terjadi.
Resume yang aman memerlukan identitas representasi
Anggap client sudah menyimpan byte 0 sampai 4.999 dan menginginkan sisanya. Mengirim Range: bytes=5000- hanya menjawab pertanyaan tentang offset. Hal itu tidak membuktikan bahwa resource masih memiliki representasi yang sama.
If-Range menghubungkan kedua persoalan tersebut. Dengan strong entity tag yang sebelumnya diterima dari server, request dapat terlihat seperti ini:
GET /downloads/archive.tar HTTP/1.1
Host: example.test
Range: bytes=5000-
If-Range: "release-42"
Jika validator masih cocok, server dapat memproses range dan mengembalikan 206. Jika tidak cocok, server mengabaikan range dan mengirim representasi terbaru secara lengkap, biasanya sebagai 200. Fallback ini memang disengaja: client dapat mengganti salinan parsial yang sudah usang alih-alih mencampur byte dari versi berbeda.
Weak entity tag, yang dapat dikenali dari awalan W/, tidak boleh dikirim dalam If-Range. HTTP date juga hanya diperbolehkan dalam syarat strong validator menurut spesifikasi dan ketika client tidak memiliki entity tag. Dalam praktiknya, strong ETag menjadi pilihan yang lebih jelas jika aplikasi dapat membuatnya berubah setiap kali data representasi yang terlihat berubah.
Hal ini tidak otomatis menghilangkan setiap race saat resume. Server harus memilih dan mentransfer representasi secara konsisten, sedangkan validator harus benar-benar menjelaskan representasi tersebut. Bentuk terenkode yang berbeda juga memerlukan strong tag berbeda jika data representasinya berbeda.
Multiple range menambah tingkat kerumitan yang berbeda
Client dapat meminta lebih dari satu interval dalam satu header. Respons yang berhasil kemudian memakai multipart/byteranges, lengkap dengan boundary dan Content-Range terpisah di dalam setiap bagian body. Ini bukan satu potongan byte dengan beberapa celah yang dibuang.
Fitur tersebut memiliki kegunaan yang sah, tetapi juga menambah pekerjaan parsing dan pembuatan respons. RFC 9110 mengizinkan server mengabaikan, menggabungkan, atau menolak kumpulan yang berlebihan, misalnya banyak range kecil atau lebih dari dua range yang tumpang tindih. Bagian keamanan RFC mencatat bahwa request kecil dapat menimbulkan biaya memori, bandwidth, dan pemrosesan yang tidak sebanding.
Untuk endpoint download kecil yang hanya membutuhkan pause dan resume, mendukung satu range sering menjadi batas rekayasa yang masuk akal. Ini adalah pertimbangan desain, bukan persyaratan HTTP. NGINX menyediakan directive max_ranges: dokumentasi resmi core module menyatakan bahwa request yang melampaui jumlah yang dikonfigurasi diproses seolah tidak ada byte range, sedangkan nilai nol menonaktifkan byte range. Batas yang tepat bergantung pada aplikasi; tidak ada angka universal yang layak disalin tanpa memahami client.
Biarkan aplikasi memberi izin dan file server mentransfer
File statis publik biasanya dapat dilayani langsung oleh web server yang mampu menanganinya. Download privat menambah keputusan otorisasi: aplikasi perlu menentukan apakah pengguna ini boleh mengakses resource tersebut. Namun, itu tidak selalu berarti PHP harus mem-parsing setiap range dan melakukan streaming setiap byte.
Salah satu pola NGINX yang praktis adalah location internal. Aplikasi mengautentikasi request, memetakan identifier resource yang opaque ke path internal tepercaya, lalu, jika akses diizinkan, mengembalikan header respons X-Accel-Redirect. NGINX kemudian menjalankan internal request dan melayani file. Dokumentasi core-nya menyebut X-Accel-Redirect sebagai salah satu sumber internal request; request eksternal tidak dapat mengambil location internal secara langsung.
Pembagian ini mempertahankan otorisasi di dalam kode aplikasi sambil mendelegasikan detail transfer file ke server. Pola tersebut bukan pengganti keamanan path. Nama file yang dikendalikan pengguna tidak boleh langsung menjadi filesystem path atau URI internal tanpa pemeriksaan, dan setiap request download baru, termasuk request resume, tetap memerlukan otorisasi yang sesuai.
Implementasi PHP buatan sendiri dapat dibenarkan ketika kebutuhan storage atau transformasi menghalangi delegasi. Namun, parser production harus menangani nilai desimal besar tanpa integer overflow, sintaks tidak valid, open-ended range dan suffix range, conditional request, metadata respons, multiple range atau kebijakan eksplisit untuk menolaknya, kegagalan streaming, serta model otorisasi. Snippet singkat yang hanya menangani bytes=start-end akan menyembunyikan lebih banyak risiko daripada yang dijelaskannya.
Uji perilaku, bukan hanya satu header
Command berikut membentuk rangkaian pemeriksaan ringkas. Ganti URL dengan resource pengujian yang tidak sensitif:
curl -I 'https://example.test/downloads/sample.bin'
curl -sS -D - -o /dev/null \
-H 'Range: bytes=0-999' \
'https://example.test/downloads/sample.bin'
curl -sS -D - -o /dev/null \
-H 'Range: bytes=999999999-' \
'https://example.test/downloads/sample.bin'
Periksa status, Content-Range, Content-Length, ETag, dan content coding sebagai satu kesatuan. Setelah itu, uji If-Range yang cocok dan tidak cocok, kegagalan otorisasi, representasi kosong, serta jalur proxy atau CDN yang dipakai di production. Jangan menganggap hasil langsung dari origin membuktikan perilaku intermediary.
Untuk transfer yang benar-benar terputus, dokumentasi resmi everything curl menjelaskan --continue-at -, yang menentukan offset resume dari file tujuan yang sudah ada:
curl --continue-at - -O 'https://example.test/downloads/sample.bin'
Command itu hanya berguna ketika server dan validator menjaga kontrak representasi. Sebuah tool dapat menghitung tempat berakhirnya byte lokal; tool tersebut tidak dapat membuat byte remote yang tidak cocok menjadi bagian dari versi yang sama.
Partial content layak didukung secara selektif
Dukungan range berguna untuk download besar yang immutable, pencarian posisi pada media, dan client yang benar-benar memperoleh manfaat dari pengambilan parsial. Dukungan ini bisa tidak perlu untuk respons dinamis kecil, representasi hasil generate yang identitasnya sulit dijaga tetap stabil, atau endpoint tempat respons lengkap sudah cukup sederhana dan murah.
Model mental yang andal sebenarnya sederhana: client meminta sebagian dari representasi terpilih; server boleh memenuhi atau mengabaikan permintaan tersebut; status dan metadata respons memberi tahu client tentang hasilnya; dan strong validator mencegah resume download diam-diam melintasi versi representasi. Ketika semua bagian itu diperlakukan sebagai satu kontrak, 206 Partial Content tidak lagi terlihat seperti trik optimasi, melainkan seperti fungsi sebenarnya: pernyataan presisi tentang byte mana yang sedang dikirim.
References
- Fielding, R., Nottingham, M., and Reschke, J. RFC 9110: HTTP Semantics, Range Requests. IETF / RFC Editor, June 2022.
- MDN contributors. HTTP range requests. MDN Web Docs, last modified July 28, 2026.
- NGINX. Module ngx_http_core_module. Official documentation.
- Stenberg, D., and contributors. Resuming and ranges. everything curl.
