Buka kekuatan FastAPI untuk unggahan file formulir multipart yang efisien. Panduan komprehensif ini mencakup praktik terbaik, penanganan kesalahan, dan teknik lanjutan.
Menguasai Unggahan File FastAPI: Pendalaman Pemrosesan Formulir Multipart
Dalam aplikasi web modern, kemampuan untuk menangani unggahan file adalah persyaratan mendasar. Baik itu pengguna yang mengirimkan gambar profil, dokumen untuk diproses, atau media untuk dibagikan, mekanisme unggah file yang kuat dan efisien sangat penting. FastAPI, framework web Python berkinerja tinggi, unggul dalam domain ini, menawarkan cara yang efisien untuk mengelola data formulir multipart, yang merupakan standar untuk mengirim file melalui HTTP. Panduan komprehensif ini akan memandu Anda melalui seluk-beluk unggahan file FastAPI, dari implementasi dasar hingga pertimbangan lanjutan, memastikan Anda dapat dengan percaya diri membangun API yang kuat dan terukur untuk audiens global.
Memahami Data Formulir Multipart
Sebelum menyelami implementasi FastAPI, penting untuk memahami apa itu data formulir multipart. Ketika browser web mengirimkan formulir yang berisi file, biasanya menggunakan atribut enctype="multipart/form-data". Jenis pengkodean ini memecah pengiriman formulir menjadi beberapa bagian, masing-masing dengan jenis konten dan informasi disposisi sendiri. Hal ini memungkinkan pengiriman berbagai jenis data dalam satu permintaan HTTP, termasuk kolom teks, kolom non-teks, dan file biner.
Setiap bagian dalam permintaan multipart terdiri dari:
- Header Content-Disposition: Menentukan nama field formulir (
name) dan, untuk file, nama file asli (filename). - Header Content-Type: Menunjukkan jenis MIME dari bagian tersebut (misalnya,
text/plain,image/jpeg). - Body: Data aktual untuk bagian tersebut.
Pendekatan FastAPI untuk Unggahan File
FastAPI memanfaatkan pustaka standar Python dan berintegrasi secara mulus dengan Pydantic untuk validasi data. Untuk unggahan file, ia menggunakan tipe UploadFile dari modul fastapi. Kelas ini menyediakan antarmuka yang nyaman dan aman untuk mengakses data file yang diunggah.
Implementasi Unggahan File Dasar
Mari kita mulai dengan contoh sederhana tentang cara membuat endpoint di FastAPI yang menerima satu unggahan file. Kita akan menggunakan fungsi File dari fastapi untuk mendeklarasikan parameter file.
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: UploadFile):
return {"filename": file.filename, "content_type": file.content_type}
Dalam contoh ini:
- Kami mengimpor
FastAPI,File, danUploadFile. - Endpoint
/files/didefinisikan sebagai permintaanPOST. - Parameter
filedianotasi denganUploadFile, yang menandakan bahwa ia mengharapkan unggahan file. - Di dalam fungsi endpoint, kita dapat mengakses properti file yang diunggah seperti
filenamedancontent_type.
Ketika klien mengirimkan permintaan POST ke /files/ dengan file terlampir (biasanya melalui formulir dengan enctype="multipart/form-data"), FastAPI akan secara otomatis menangani penguraian dan menyediakan objek UploadFile. Anda kemudian dapat berinteraksi dengan objek ini.
Menyimpan File yang Diunggah
Seringkali, Anda perlu menyimpan file yang diunggah ke disk atau memproses kontennya. Objek UploadFile menyediakan metode untuk ini:
read(): Membaca seluruh konten file ke dalam memori sebagai byte. Gunakan ini untuk file yang lebih kecil.write(content: bytes): Menulis byte ke file.seek(offset: int): Mengubah posisi file saat ini.close(): Menutup file.
Penting untuk menangani operasi file secara asinkron, terutama saat berhadapan dengan file besar atau tugas yang terikat I/O. UploadFile FastAPI mendukung operasi asinkron.
from fastapi import FastAPI, File, UploadFile
import shutil
app = FastAPI()
@app.post("/files/save/")
async def save_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"file '{file.filename}' saved at '{file_location}'"}
Dalam contoh yang disempurnakan ini:
- Kami menggunakan
File(...)untuk menunjukkan bahwa parameter ini diperlukan. - Kami menentukan jalur lokal tempat file akan disimpan. Pastikan direktori
uploadsada. - Kami membuka file tujuan dalam mode tulis biner (`"wb+"`).
- Kami secara asinkron membaca konten file yang diunggah menggunakan
await file.read()dan kemudian menuliskannya ke file lokal.
Catatan: Membaca seluruh file ke dalam memori dengan await file.read() mungkin bermasalah untuk file yang sangat besar. Untuk skenario seperti itu, pertimbangkan untuk melakukan streaming konten file.
Streaming Konten File
Untuk file besar, membaca seluruh konten ke dalam memori dapat menyebabkan konsumsi memori yang berlebihan dan potensi kesalahan kehabisan memori. Pendekatan yang lebih hemat memori adalah dengan melakukan streaming file secara bertahap. Fungsi shutil.copyfileobj sangat baik untuk ini, tetapi kita perlu mengadaptasinya untuk operasi asinkron.
from fastapi import FastAPI, File, UploadFile
import aiofiles # Install using: pip install aiofiles
app = FastAPI()
@app.post("/files/stream/")
async def stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
content = await file.read()
await out_file.write(content)
return {"info": f"file '{file.filename}' streamed and saved at '{file_location}'"}
Dengan aiofiles, kita dapat secara efisien melakukan streaming konten file yang diunggah ke file tujuan tanpa memuat seluruh file ke dalam memori sekaligus. await file.read() dalam konteks ini masih membaca seluruh file, tetapi aiofiles menangani penulisan dengan lebih efisien. Untuk streaming chunk demi chunk yang sebenarnya dengan UploadFile, Anda biasanya akan melakukan iterasi melalui await file.read(chunk_size), tetapi aiofiles.open dan await out_file.write(content) adalah pola umum dan berkinerja tinggi untuk menyimpan.
Pendekatan streaming yang lebih eksplisit menggunakan chunking:
from fastapi import FastAPI, File, UploadFile
import aiofiles
app = FastAPI()
CHUNK_SIZE = 1024 * 1024 # 1MB chunk size
@app.post("/files/chunked_stream/")
async def chunked_stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
while content := await file.read(CHUNK_SIZE):
await out_file.write(content)
return {"info": f"file '{file.filename}' chunked streamed and saved at '{file_location}'"}
Endpoint `chunked_stream_file` ini membaca file dalam potongan 1MB dan menulis setiap potongan ke file output. Ini adalah cara yang paling hemat memori untuk menangani file yang berpotensi sangat besar.
Menangani Banyak Unggahan File
Aplikasi web sering kali mengharuskan pengguna untuk mengunggah beberapa file secara bersamaan. FastAPI membuat ini mudah.
Mengunggah Daftar File
Anda dapat menerima daftar file dengan menganotasi parameter Anda dengan daftar UploadFile.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/multiple/")
async def create_multiple_files(
files: List[UploadFile] = File(...)
):
results = []
for file in files:
# Process each file, e.g., save it
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {"files_processed": results}
Dalam skenario ini, klien perlu mengirimkan beberapa bagian dengan nama field formulir yang sama (misalnya, `files`). FastAPI akan mengumpulkannya ke dalam daftar Python objek UploadFile.
Mencampur File dan Data Formulir Lainnya
Umum untuk memiliki formulir yang berisi field file dan field teks reguler. FastAPI menangani ini dengan memungkinkan Anda mendeklarasikan parameter lain menggunakan anotasi tipe standar, bersama dengan Form untuk field formulir yang bukan file.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/mixed/")
async def upload_mixed_data(
description: str = Form(...),
files: List[UploadFile] = File(...) # Accepts multiple files with the name 'files'
):
results = []
for file in files:
# Process each file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {
"description": description,
"files_processed": results
}
Saat menggunakan alat seperti Swagger UI atau Postman, Anda akan menentukan description sebagai field formulir reguler dan kemudian menambahkan beberapa bagian untuk field files, masing-masing dengan jenis konten yang disetel ke jenis gambar/dokumen yang sesuai.
Fitur Lanjutan dan Praktik Terbaik
Di luar penanganan file dasar, beberapa fitur lanjutan dan praktik terbaik sangat penting untuk membangun API unggah file yang kuat.
Batas Ukuran File
Mengizinkan unggahan file tanpa batas dapat menyebabkan serangan penolakan layanan atau konsumsi sumber daya yang berlebihan. Meskipun FastAPI sendiri tidak memberlakukan batasan keras secara default di tingkat framework, Anda harus menerapkan pemeriksaan:
- Di Tingkat Aplikasi: Periksa ukuran file setelah diterima tetapi sebelum diproses atau disimpan.
- Di Tingkat Server/Proxy Web: Konfigurasikan server web Anda (misalnya, Nginx, Uvicorn dengan worker) untuk menolak permintaan yang melebihi ukuran payload tertentu.
Contoh pemeriksaan ukuran tingkat aplikasi:
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
MAX_FILE_SIZE_MB = 10
MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024
@app.post("/files/limited_size/")
async def upload_with_size_limit(file: UploadFile = File(...)):
if len(await file.read()) > MAX_FILE_SIZE_BYTES:
raise HTTPException(status_code=400, detail=f"File is too large. Maximum size is {MAX_FILE_SIZE_MB}MB.")
# Reset file pointer to read content again
await file.seek(0)
# Proceed with saving or processing the file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully."}
Penting: Setelah membaca file untuk memeriksa ukurannya, Anda harus menggunakan await file.seek(0) untuk mengatur ulang pointer file ke awal jika Anda ingin membaca kontennya lagi (misalnya, untuk menyimpannya).
Jenis File yang Diizinkan (Jenis MIME)
Membatasi unggahan ke jenis file tertentu meningkatkan keamanan dan memastikan integritas data. Anda dapat memeriksa atribut content_type dari objek UploadFile.
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
ALLOWED_FILE_TYPES = {"image/jpeg", "image/png", "application/pdf"}
@app.post("/files/restricted_types/")
async def upload_restricted_types(file: UploadFile = File(...)):
if file.content_type not in ALLOWED_FILE_TYPES:
raise HTTPException(status_code=400, detail=f"Unsupported file type: {file.content_type}. Allowed types are: {', '.join(ALLOWED_FILE_TYPES)}")
# Proceed with saving or processing the file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully and is of an allowed type."}
Untuk pemeriksaan tipe yang lebih kuat, terutama untuk gambar, Anda dapat mempertimbangkan untuk menggunakan pustaka seperti Pillow untuk memeriksa konten aktual file, karena jenis MIME terkadang dapat dipalsukan.
Penanganan Kesalahan dan Umpan Balik Pengguna
Berikan pesan kesalahan yang jelas dan dapat ditindaklanjuti kepada pengguna. Gunakan HTTPException FastAPI untuk respons kesalahan HTTP standar.
- File Tidak Ditemukan/Hilang: Jika parameter file yang diperlukan tidak dikirim.
- Ukuran File Terlampaui: Seperti yang ditunjukkan dalam contoh batas ukuran.
- Jenis File Tidak Valid: Seperti yang ditunjukkan dalam contoh pembatasan tipe.
- Kesalahan Server: Untuk masalah selama penyimpanan atau pemrosesan file (misalnya, disk penuh, kesalahan izin).
Pertimbangan Keamanan
Unggahan file memperkenalkan risiko keamanan:
- File Berbahaya: Mengunggah file yang dapat dieksekusi (
.exe,.sh) atau skrip yang disamarkan sebagai jenis file lain. Selalu validasi jenis file dan pertimbangkan untuk memindai file yang diunggah untuk mencari malware. - Path Traversal: Bersihkan nama file untuk mencegah penyerang mengunggah file ke direktori yang tidak diinginkan (misalnya, menggunakan nama file seperti
../../etc/passwd).UploadFileFastAPI menangani pembersihan nama file dasar, tetapi kehati-hatian ekstra sangat bijaksana. - Denial of Service: Terapkan batasan ukuran file dan berpotensi membatasi kecepatan pada endpoint unggah.
- Cross-Site Scripting (XSS): Jika Anda menampilkan nama file atau konten file langsung di halaman web, pastikan mereka di-escape dengan benar untuk mencegah serangan XSS.
Praktik Terbaik: Simpan file yang diunggah di luar root dokumen server web Anda, dan sajikan melalui endpoint khusus dengan kontrol akses yang sesuai, atau gunakan Content Delivery Network (CDN).
Menggunakan Model Pydantic dengan Unggahan File
Meskipun UploadFile adalah tipe utama untuk file, Anda dapat mengintegrasikan unggahan file ke dalam model Pydantic untuk struktur data yang lebih kompleks. Namun, field unggah file langsung dalam model Pydantic standar tidak didukung secara native untuk formulir multipart. Sebagai gantinya, Anda biasanya menerima file sebagai parameter terpisah dan kemudian berpotensi memprosesnya ke dalam format yang dapat disimpan atau divalidasi oleh model Pydantic.
Pola umum adalah memiliki model Pydantic untuk metadata dan kemudian menerima file secara terpisah:
from fastapi import FastAPI, File, UploadFile, Form
from pydantic import BaseModel
from typing import Optional
class UploadMetadata(BaseModel):
title: str
description: Optional[str] = None
app = FastAPI()
@app.post("/files/model_metadata/")
async def upload_with_metadata(
metadata: str = Form(...), # Receive metadata as a JSON string
file: UploadFile = File(...)
):
import json
try:
metadata_obj = UploadMetadata(**json.loads(metadata))
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON format for metadata")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error parsing metadata: {e}")
# Now you have metadata_obj and file
# Proceed with saving file and using metadata
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {
"message": "File uploaded successfully with metadata",
"metadata": metadata_obj,
"filename": file.filename
}
Dalam pola ini, klien mengirimkan metadata sebagai string JSON dalam field formulir (misalnya, metadata) dan file sebagai bagian multipart terpisah. Server kemudian mengurai string JSON ke dalam objek Pydantic.
Unggahan File Besar dan Chunking
Untuk file yang sangat besar (misalnya, gigabyte), bahkan streaming mungkin mencapai batasan server web atau sisi klien. Teknik yang lebih canggih adalah unggahan chunk, di mana klien memecah file menjadi potongan-potongan yang lebih kecil dan mengunggahnya secara berurutan atau paralel. Server kemudian menyusun kembali potongan-potongan ini. Ini biasanya memerlukan logika sisi klien khusus dan endpoint server yang dirancang untuk menangani manajemen chunk (misalnya, mengidentifikasi chunk, penyimpanan sementara, dan perakitan akhir).
Meskipun FastAPI tidak menyediakan dukungan bawaan untuk unggahan chunk yang dimulai klien, Anda dapat menerapkan logika ini di dalam endpoint FastAPI Anda. Ini melibatkan pembuatan endpoint yang:
- Menerima potongan file individual.
- Menyimpan potongan-potongan ini sementara, mungkin dengan metadata yang menunjukkan urutan dan jumlah total potongan.
- Menyediakan endpoint atau mekanisme untuk memberi sinyal ketika semua potongan telah diunggah, memicu proses perakitan kembali.
Ini adalah tugas yang lebih kompleks dan sering kali melibatkan pustaka JavaScript di sisi klien.
Pertimbangan Internasionalisasi dan Globalisasi
Saat membangun API untuk audiens global, unggahan file memerlukan perhatian khusus:
- Nama File: Pengguna di seluruh dunia dapat menggunakan karakter non-ASCII dalam nama file (misalnya, aksen, ideogram). Pastikan sistem Anda menangani dan menyimpan nama file ini dengan benar. Pengkodean UTF-8 umumnya standar, tetapi kompatibilitas mendalam mungkin memerlukan pengkodean/dekode dan pembersihan yang cermat.
- Satuan Ukuran File: Meskipun MB dan GB umum, perhatikan bagaimana pengguna memahami ukuran file. Menampilkan batasan dengan cara yang mudah digunakan sangat penting.
- Jenis Konten: Pengguna mungkin mengunggah file dengan jenis MIME yang kurang umum. Pastikan daftar jenis yang diizinkan Anda komprehensif atau cukup fleksibel untuk kasus penggunaan Anda.
- Peraturan Regional: Waspadai undang-undang dan peraturan residensi data di berbagai negara. Menyimpan file yang diunggah mungkin memerlukan kepatuhan terhadap aturan ini.
- Antarmuka Pengguna: Antarmuka sisi klien untuk mengunggah file harus intuitif dan mendukung bahasa dan lokal pengguna.
Alat dan Pustaka untuk Pengujian
Menguji endpoint unggah file sangat penting. Berikut adalah beberapa alat umum:
- Swagger UI (Dokumen API Interaktif): FastAPI secara otomatis menghasilkan dokumentasi Swagger UI. Anda dapat langsung menguji unggahan file dari antarmuka browser. Cari field input file dan klik tombol "Pilih File".
- Postman: Alat pengembangan dan pengujian API yang populer. Untuk mengirim permintaan unggah file:
- Setel metode permintaan ke POST.
- Masukkan URL endpoint API Anda.
- Buka tab "Body".
- Pilih "form-data" sebagai tipe.
- Dalam pasangan kunci-nilai, masukkan nama parameter file Anda (misalnya,
file). - Ubah tipe dari "Text" menjadi "File".
- Klik "Pilih File" untuk memilih file dari sistem lokal Anda.
- Jika Anda memiliki field formulir lain, tambahkan mereka dengan cara yang sama, dengan menjaga tipe mereka sebagai "Text".
- Kirim permintaan.
- cURL: Alat baris perintah untuk membuat permintaan HTTP.
- Untuk satu file:
curl -X POST -F "file=@/path/to/your/local/file.txt" http://localhost:8000/files/ - Untuk beberapa file:
curl -X POST -F "files=@/path/to/file1.txt" -F "files=@/path/to/file2.png" http://localhost:8000/files/multiple/ - Untuk data campuran:
curl -X POST -F "description=My description" -F "files=@/path/to/file.txt" http://localhost:8000/files/mixed/ - Pustaka `requests` Python: Untuk pengujian terprogram.
import requests
url = "http://localhost:8000/files/save/"
files = {'file': open('/path/to/your/local/file.txt', 'rb')}
response = requests.post(url, files=files)
print(response.json())
# For multiple files
url_multiple = "http://localhost:8000/files/multiple/"
files_multiple = {
'files': [('file1.txt', open('/path/to/file1.txt', 'rb')),
('image.png', open('/path/to/image.png', 'rb'))]
}
response_multiple = requests.post(url_multiple, files=files_multiple)
print(response_multiple.json())
# For mixed data
url_mixed = "http://localhost:8000/files/mixed/"
data = {'description': 'Test description'}
files_mixed = {'files': open('/path/to/another_file.txt', 'rb')}
response_mixed = requests.post(url_mixed, data=data, files=files_mixed)
print(response_mixed.json())
Kesimpulan
FastAPI menyediakan cara yang kuat, efisien, dan intuitif untuk menangani unggahan file multipart. Dengan memanfaatkan tipe UploadFile dan pemrograman asinkron, pengembang dapat membangun API yang kuat yang secara mulus mengintegrasikan kemampuan penanganan file. Ingatlah untuk memprioritaskan keamanan, menerapkan penanganan kesalahan yang sesuai, dan mempertimbangkan kebutuhan basis pengguna global dengan menangani aspek-aspek seperti pengkodean nama file dan kepatuhan terhadap peraturan.
Baik Anda membangun layanan berbagi gambar sederhana atau platform pemrosesan dokumen yang kompleks, menguasai fitur unggah file FastAPI akan menjadi aset yang signifikan. Teruslah menjelajahi kemampuannya, menerapkan praktik terbaik, dan memberikan pengalaman pengguna yang luar biasa untuk audiens internasional Anda.