Skip to content

Batas Upload 8 MiB: Satu Angka, Empat Lapisan

Adityo Guni Waluyo

Satu batas upload bukan satu setting: FastAPI ternyata tidak menolak berkas besar. Gerbang aslinya ada di browser, nginx, dan Next.js.

Ringkasan

Unggahan 8,5 MiB di KotaPortal gagal nyeleneh, tadinya dikira FastAPI yang nolak. Ternyata FastAPI nggak nolak otomatis, gerbangnya ada di empat tempat: browser, nginx (default 1 MiB), Server Actions Next.js (default 1 MB), dan kode aplikasi. Solusinya gampang: samain konstanta 8 MiB di keempat lapisan biar nggak ada celah.

Saat meninjau log server proyek KotaPortal, saya menemukan pola kegagalan unggah yang tidak konsisten. Berkas berukuran tepat di atas 8 MiB kadang lolos, kadang ditolak tanpa pesan error yang jelas di antarmuka.

Asumsi awal saya sederhana: lapisan API (FastAPI) pasti punya pengaturan batas per berkas yang terlalu ketat dan menolak langsung di tingkat aplikasi sebelum data sempat diproses.

Temuannya masuk lewat laporan QA: satu skenario mengunggah lampiran 8,5 MiB dan berhenti di tengah jalan tanpa pesan yang jelas, sementara skenario lain dengan berkas kecil berjalan lancar. Pertanyaan yang wajar muncul dari situ: angka batasnya sebenarnya berapa, dan lapisan mana yang memegangnya?

Fakta di lapangan membuktikan sebaliknya. Lapisan API sama sekali tidak punya penolakan otomatis berdasarkan ukuran berkas. Gerbang penolakan yang sebenarnya ada di tiga tempat lain, dan menyelaraskan semuanya dengan satu angka yang sama adalah pekerjaan yang sebenarnya.

Asumsi yang Meleset

Kesalahpahaman berpusat pada perilaku framework backend. FastAPI menangani unggahan sebagai data form yang butuh paket python-multipart [3]. Jika parameter dideklarasikan sebagai bytes, seluruh isi berkas dibaca ke memori sekaligus; UploadFile jauh lebih efisien karena menampung berkas kecil di memori lalu memindahkan berkas besar ke disk sementara saat ambang spool tercapai.

Di sinilah kejutan teknisnya. Parser multipart Starlette memang punya max_part_size 1 MB, tapi error "Part exceeded maximum size of 1024KB." hanya berlaku untuk part non-berkas [4]. Untuk part berkas, data dialirkan ke SpooledTemporaryFile, dan max_size-nya cuma menentukan kapan data pindah dari memori ke disk, bukan jadi batas penolakan. max_files dan max_fields dibatasi 1000 entri, tapi tidak ada mekanisme bawaan yang menolak berkas besar di lapisan ini. Aplikasi harus memeriksa ukuran secara eksplisit dengan kodenya sendiri.

Empat Gerbang yang Harus Selaras

Menetapkan batas 8 MiB (tepatnya 8.388.608 byte) bukan sekadar mengubah satu variabel. Ini keputusan arsitektur untuk menyelaraskan empat lapisan yang masing-masing diam-diam punya angka defaultnya sendiri.

Pertama, gerbang paling ramah pengguna ada di browser. Objek File di JavaScript mewarisi properti size dari Blob: hanya-baca, dalam byte, dan didukung luas sejak Juli 2015 [5]. Memvalidasi ukuran sebelum satu byte pun dikirim menghemat bandwidth dan memberi umpan balik instan, tanpa membebani server.

Kedua, berkas yang lolos dari browser akan bertemu proxy. Default client_max_body_size nginx adalah 1 MiB; melebihi itu berarti respons 413 Request Entity Too Large, dan dokumentasinya sendiri mengingatkan bahwa browser tidak bisa menampilkan error ini dengan benar [2], jadi kegagalannya terasa seperti aplikasi macet. Direktif ini bisa ditaruh di konteks http, server, atau location, dan nilainya harus dinaikkan eksplisit agar unggahan 8 MiB lolos.

Ketiga, lapisan frontend. Server Actions Next.js membatasi body permintaan 1 MB secara default [1], dan batasnya berlaku untuk body HTTP mentah, termasuk byte yang ditambahkan boundary, header part, dan metadata field multipart. Dokumentasinya menyarankan menyisakan ruang sekitar 10 sampai 20 KB untuk overhead itu. Angkanya bisa diatur lewat serverActions.bodySizeLimit, misalnya string 3mb atau nilai byte biasa.

Keempat, pertahanan terakhir di kode aplikasi FastAPI. Karena lapisan di bawahnya tidak menolak berkas besar otomatis, kode harus membaca ukuran dari objek unggahan dan menolak sendiri ketika melewati 8 MiB yang disepakati di spesifikasi proyek. Polanya sederhana: begitu UploadFile diterima, baca size-nya, bandingkan dengan konstanta 8 MiB yang sama yang dipakai di browser dan nginx, lalu tolak dengan respons 413 versi aplikasi sebelum berkas disentuh lebih jauh. Kuncinya bukan tekniknya, tapi konsistensi angkanya: satu konstanta yang sama, dibaca empat lapisan.

Melewatkan salah satu lapisan berarti meninggalkan celah di mana unggahan gagal dengan cara yang sulit dilacak. Empat lapisan yang membaca angka yang sama membuat batas 8 MiB berlaku konsisten, dari klik pertama pengguna sampai berkas mendarat di server.

Sumber

  1. Next.js Docs: serverActions (bodySizeLimit)
  2. nginx Docs: ngx_http_core_module (client_max_body_size)
  3. FastAPI Tutorial: Request Files
  4. Starlette: formparsers.py
  5. MDN: Blob.size

Artikel terkait