آپلود فایل از اون بخشهاییه که در ظاهر فقط چند خط کد میخواد، اما وقتی حجم فایلها بالا میره خیلی سریع میتونه تبدیل به دردسر حافظه و Performance بشه. یه عکس چندصد کیلوبایتی مشکلی ایجاد نمیکنه، ولی وقتی کاربر فایل ویدیویی، PDF حجیم یا آرشیو چندصد مگابایتی میفرسته، نحوه دریافت فایل داخل FastAPI اهمیت زیادی پیدا میکنه؛ چون اگه کل محتوا رو یکجا وارد RAM کنید، با چند کاربر همزمان ممکنه مصرف حافظه سرور ناگهان بالا بره.
FastAPI برای همین سناریو ابزاری به اسم UploadFile داره که برای مدیریت فایلهای آپلودی، مخصوصاً فایلهای بزرگ، انتخاب مناسبتری نسبت به دریافت مستقیم محتوا به شکل bytes محسوب میشه.
تفاوت bytes و UploadFile در FastAPI چیست؟
اگه پارامتر فایل رو به شکل bytes دریافت کنید، FastAPI محتوای کامل فایل رو میخونه و به صورت داده باینری داخل حافظه قرار میده. این روش برای فایلهای کوچک ساده و کاربردیه، ولی برای فایلهای بزرگ میتونه RAM زیادی مصرف کنه، چون کل فایل باید همزمان در حافظه حضور داشته باشه. مستندات رسمی FastAPI هم صراحتاً اشاره میکنن که دریافت فایل به شکل bytes باعث نگهداری کامل محتوا در حافظه میشه.
در مقابل، UploadFile از یه فایل موقت هوشمند استفاده میکنه؛ یعنی تا یه اندازه مشخص داده میتونه در حافظه نگه داشته بشه و وقتی حجم بیشتر شد، فایل روی دیسک موقت قرار میگیره. این مدل برای عکسهای بزرگ، ویدیو، فایلهای باینری و فایلهایی که نمیخواید یکجا RAM رو اشغال کنن مناسبتره.
UploadFile چه اطلاعاتی در اختیار ما میگذارد؟
وقتی FastAPI فایل رو به شکل UploadFile تحویل میده، فقط به محتوای فایل دسترسی ندارید؛ اطلاعاتی مثل نام اصلی فایل، نوع محتوای فایل و خود شیء فایل هم در دسترس هستن. مثلاً filename نام فایل آپلودشده رو میده و content_type مشخص میکنه فایل چه نوع رسانهای داره، مثل تصویر JPEG یا PDF.
همچنین متدهایی مثل read()، write()، seek() و close() در اختیار شما هستن و در Endpointهای غیرهمزمان میشه اونها رو با await استفاده کرد. این موضوع اجازه میده فایل رو مرحلهای بخونید و مجبور نباشید همیشه کل محتوا رو یکجا وارد حافظه کنید.
نمونه آپلود امنتر فایل در FastAPI
یه Endpoint ساده برای دریافت فایل، بررسی نوع اون و ذخیره مرحلهای روی سرور میتونه این شکلی باشه:
from pathlib import Path
from fastapi import FastAPI, UploadFile, File, HTTPException
app = FastAPI()
UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
ALLOWED_TYPES = {
"image/jpeg",
"image/png",
"application/pdf"
}
@app.post("/upload/")
async def upload_file(file: UploadFile = File(...)):
if file.content_type not in ALLOWED_TYPES:
raise HTTPException(
status_code=400,
detail="File type is not allowed"
)
safe_name = Path(file.filename).name
destination = UPLOAD_DIR / safe_name
with destination.open("wb") as output:
while chunk := await file.read(1024 * 1024):
output.write(chunk)
await file.close()
return {
"filename": safe_name,
"content_type": file.content_type
}
اینجا فایل رو یکجا با read() بدون اندازه نمیخونیم، بلکه هر بار یک مگابایت دریافت و ذخیره میکنیم. به این روش خواندن مرحلهای داده گفته میشه که معمولاً با اصطلاح Chunked Reading شناخته میشه و کمک میکنه مقدار حافظه مصرفی قابلکنترلتر بمونه.
فقط پسوند فایل را بررسی نکنید
یکی از اشتباههای رایج اینه که فقط ببینیم اسم فایل با .jpg یا .pdf تموم میشه و بر اساس اون تصمیم بگیریم فایل امنه. اسم فایل رو کاربر مشخص میکنه و تغییر دادنش خیلی راحته، بنابراین پسوند به تنهایی معیار قابل اعتمادی برای تشخیص نوع واقعی فایل نیست.
content_type اطلاعات بهتری در اختیارمون میذاره، ولی برای سیستمهایی که امنیت فایل اهمیت زیادی داره، حتی همین مقدار هم نباید تنها معیار باشه؛ چون این اطلاعات هم از سمت Request میاد. برای فایلهای حساس بهتره محتوای واقعی فایل بررسی بشه یا از کتابخونهای استفاده کنید که نوع فایل رو از روی ساختارش تشخیص بده.
محدودیت حجم فایل را قبل از دردسر مشخص کنید
اینکه UploadFile حافظه رو بهتر مدیریت میکنه به این معنی نیست که باید فایل با هر حجمی رو قبول کنید. اگه API فقط برای عکس پروفایله، منطقی نیست کاربر بتونه فایل چند گیگابایتی ارسال کنه.
بهتره محدودیت حجم در چند لایه داشته باشید؛ مثلاً روی Reverse Proxy مثل Nginx، روی تنظیمات زیرساخت و در صورت نیاز داخل خود برنامه. این کار هم جلوی مصرف بیدلیل پهنای باند و دیسک رو میگیره و هم سطح حمله رو کمتر میکنه.
نام فایل کاربر را مستقیم اعتماد نکنید
اسم فایل هم بخشی از ورودی کاربره. ذخیره مستقیم file.filename بدون بررسی میتونه باعث مشکلات مسیر فایل یا جایگزین شدن فایلهای قبلی بشه. حداقل باید اسم فایل پاکسازی بشه و در پروژههای جدیتر بهتره یه نام جدید و منحصربهفرد برای فایل ساخته بشه، مثلاً با UUID.
مسیر ذخیره فایل هم نباید اجازه بده کاربر با اسمهایی مثل ../ از پوشه آپلود خارج بشه. در نمونه بالا از Path(file.filename).name استفاده کردیم تا فقط بخش نهایی نام فایل باقی بمونه، ولی در پروژه واقعی هنوز بهتره نام نهایی رو خود سرور تولید کنه.
چه زمانی Streaming اهمیت بیشتری پیدا میکند؟
وقتی فایلها خیلی بزرگ هستن یا اصلاً نمیخواید نسخه کامل اونها روی سرور برنامه ذخیره بشه، بهتره داده رو به صورت جریان پیوسته پردازش کنید. به این مدل پردازش داده همزمان با دریافت گفته میشه که اصطلاحاً Streaming نام داره.
مثلاً میشه فایل رو در چند بخش دریافت کرد و همزمان به Object Storage مثل S3 فرستاد، یا بدون ذخیره کامل روی دیسک، بخشهای مختلف اون رو پردازش کرد. این معماری برای فایلهای چند گیگابایتی منطقیتر از اینه که اول کل فایل رو روی سرور API نگه دارید و بعد تازه پردازش رو شروع کنید.
UploadFile همه مشکلات آپلود را حل نمیکند
UploadFile بیشتر مشکل نحوه دریافت و مدیریت حافظه رو بهتر میکنه، ولی امنیت و معماری آپلود همچنان مسئولیت شماست. باید نوع فایل، حجم، نام، محل ذخیره، سطح دسترسی و حتی نحوه پاکسازی فایلهای موقت یا قدیمی رو مشخص کنید.
همچنین اگر بعد از آپلود قراره عملیات سنگینی مثل تبدیل ویدیو، استخراج فایل ZIP یا پردازش تصویر انجام بشه، بهتره این کارها Request کاربر رو معطل نکنن و به Worker یا Queue جدا منتقل بشن.
جمعبندی
برای فایلهای کوچک، دریافت محتوا به شکل bytes میتونه کاملاً کافی باشه، ولی وقتی اندازه فایل قابل توجهه، UploadFile معمولاً انتخاب مناسبتریه چون از فایل موقت استفاده میکنه و مجبور نیست کل محتوا رو یکجا داخل RAM نگه داره. FastAPI همچنین اطلاعاتی مثل نام فایل، نوع محتوا و متدهای غیرهمزمان برای خواندن و مدیریت فایل در اختیار شما قرار میده.
برای یه سیستم آپلود درست، فقط دریافت فایل کافی نیست؛ باید حجم و نوع فایل محدود بشه، نام فایل قابل اعتماد در نظر گرفته نشه و برای فایلهای خیلی بزرگ از خواندن مرحلهای یا Streaming استفاده بشه. به زبان ساده، هرچقدر فایل بزرگتر میشه، اهمیت اینکه «چطور دریافتش میکنید» از خود عملیات Upload بیشتر میشه.
نظرات کاربران (0)