本文 AI 產出,尚未審核
FastAPI – File 上傳(File() 與 UploadFile)
簡介
在 Web 應用中,檔案上傳是常見需求,無論是使用者上傳頭像、PDF 報表,或是系統接收批次資料,都離不開 HTTP POST 的 multipart/form-data 格式。FastAPI 內建的 File() 與 UploadFile 讓開發者可以以 宣告式 的方式輕鬆處理檔案,同時保留非同步 I/O 的效能優勢。
本篇文章將說明如何在 FastAPI 中正確使用 File() 與 UploadFile,從基本範例到進階的檔案驗證、儲存與錯誤處理,協助你在實務專案中快速上手、寫出安全且易維護的檔案上傳 API。
核心概念
1. File() 與 UploadFile 的差異
File() |
UploadFile |
|
|---|---|---|
| 回傳型別 | bytes(完整讀入記憶體) |
UploadFile 物件(包含檔案名稱、content_type、檔案指標) |
| 適用情境 | 小檔案(例如圖示、簡短文字) | 中大型檔案(圖片、影片、CSV 等) |
| 效能 | 會一次性載入全部內容,記憶體佔用較高 | 以 stream 方式讀取,支援非同步 I/O,記憶體佔用低 |
小技巧:若不確定檔案大小,建議預設使用
UploadFile,只在確定檔案極小且需立即處理時才改用File()。
2. 基本使用方式
FastAPI 透過 依賴注入(Dependency Injection)把上傳的檔案自動轉換成相應的型別。只要在路由函式的參數上加上 File(...),框架就會把 multipart/form-data 中的檔案欄位注入進來。
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
# 1️⃣ 使用 File() 直接取得 bytes
@app.post("/upload-bytes")
async def upload_bytes(file: bytes = File(...)):
size = len(file)
return {"filename": "unknown", "size_bytes": size}
# 2️⃣ 使用 UploadFile 取得檔案資訊與檔案指標
@app.post("/upload-stream")
async def upload_stream(file: UploadFile = File(...)):
# 取得檔名與 MIME type
filename = file.filename
content_type = file.content_type
# 非同步讀取前 100 bytes 作簡易檢查
head = await file.read(100)
await file.seek(0) # 重設指標,之後可再次讀取完整內容
return {"filename": filename, "content_type": content_type, "head_bytes": len(head)}
3. 多檔案上傳
File() 與 UploadFile 都支援 list 型別,讓一次上傳多個檔案變得輕鬆。
@app.post("/upload-multiple")
async def upload_multiple(files: list[UploadFile] = File(...)):
results = []
for f in files:
content = await f.read()
results.append({"filename": f.filename, "size": len(content)})
await f.seek(0) # 若後續還要使用,可回到開頭
return {"files": results}
4. 檔案驗證與限制
雖然 FastAPI 不會自動檢查檔案大小或類型,但我們可以在端點內自行驗證,或利用 Pydantic 的自訂驗證器。
MAX_SIZE = 5 * 1024 * 1024 # 5 MB
@app.post("/upload-validate")
async def upload_validate(file: UploadFile = File(...)):
# 1. 限制 MIME type(只接受 JPEG 或 PNG 圖片)
if file.content_type not in {"image/jpeg", "image/png"}:
return {"error": "僅支援 JPEG / PNG 圖片"}
# 2. 限制檔案大小(以 stream 方式計算)
size = 0
async for chunk in file.iter_chunks():
size += len(chunk)
if size > MAX_SIZE:
return {"error": "檔案過大,請限制在 5 MB 以內"}
await file.seek(0) # 讀取完後回到開頭
# 若驗證通過,儲存檔案
path = f"uploads/{file.filename}"
async with aiofiles.open(path, "wb") as out_file:
while content := await file.read(1024):
await out_file.write(content)
return {"filename": file.filename, "size": size}
5. 非同步儲存檔案
使用 aiofiles(或其他非同步檔案 I/O 套件)可避免阻塞事件迴圈,提升 API 的併發效能。
import aiofiles
from pathlib import Path
UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
@app.post("/upload-async")
async def upload_async(file: UploadFile = File(...)):
dest_path = UPLOAD_DIR / file.filename
async with aiofiles.open(dest_path, "wb") as out_file:
while chunk := await file.read(1024): # 每次讀 1KB
await out_file.write(chunk)
return {"saved_to": str(dest_path)}
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
一次性讀入過大檔案 (File() 回傳 bytes) |
大檔案會把整個檔案放入記憶體,導致 OOM | 改用 UploadFile 並以 stream 方式處理 |
忘記重設檔案指標 (seek(0)) |
讀取部分內容後,後續再讀會從結尾開始,得到空結果 | 讀完後 await file.seek(0) 或重新開啟檔案 |
| 同步寫檔阻塞 | 使用內建 open() 會阻塞事件迴圈,降低併發 |
使用 aiofiles 或其他非同步 I/O |
| 缺乏 MIME/大小驗證 | 讓惡意使用者上傳危險檔案或過大檔案 | 在端點內自行檢查 content_type、檔案大小 |
路徑穿越攻擊 (../) |
直接使用上傳檔名作為儲存路徑,可能寫入系統敏感目錄 | 透過 os.path.basename 或 pathlib 的 secure_filename 方式過濾檔名 |
最佳實踐
- 預設使用
UploadFile,僅在確定檔案極小且需立即在記憶體內處理時才改用File()。 - 非同步寫檔:搭配
aiofiles,確保高併發環境不會被阻塞。 - 檔名安全:使用
uuid4()或時間戳記重新命名檔案,避免原始檔名衝突與路徑穿越。 - 統一錯誤回應:建立自訂例外或使用
HTTPException(status_code=400, detail=...),讓前端容易捕捉。 - 限制上傳大小:在 FastAPI 的
FastAPI(..., max_upload_size=...)(若使用 Starlette 0.24+)或自行在端點檢查。
實際應用場景
| 場景 | 為何需要檔案上傳 | 範例實作 |
|---|---|---|
| 使用者頭像 | 前端上傳圖片,後端存儲或傳至 CDN | 使用 UploadFile,驗證 MIME 為 image/*,儲存至 avatars/,回傳 CDN URL。 |
| CSV 批次匯入 | 企業系統允許管理員一次匯入大量資料 | 以 UploadFile 讀取 CSV,使用 pandas.read_csv 逐行驗證,再寫入資料庫。 |
| 影片上傳 | 影片平台需要接受大型影片檔案 | 採用 UploadFile 搭配分塊寫入(chunked upload),同時在前端實作斷點續傳。 |
| PDF 報表生成 | 後端根據使用者上傳的模板產生 PDF | 先用 UploadFile 讀取模板檔案,使用 ReportLab 或 WeasyPrint 產生 PDF,最後回傳檔案流。 |
| 機器學習模型上傳 | 使用者可自行上傳訓練好的模型檔案 | 以 UploadFile 接收 .pt、.h5 等二進位檔,儲存至安全目錄,並在服務重啟時載入。 |
總結
File()直接回傳bytes,適合小檔案;UploadFile提供檔案資訊與非同步流式讀寫,適用於大多數實務需求。- 使用 非同步 I/O(
aiofiles)可以保持 FastAPI 的高併發特性,避免因寫檔而阻塞事件迴圈。 - 檔案驗證(MIME、大小)與 安全檔名 是防止惡意上傳的重要防線。
- 依照不同 業務場景(頭像、CSV、影片、PDF、模型)選擇合適的上傳策略與儲存方式,能讓系統更具彈性與可維護性。
掌握了這些概念與實作技巧後,你就能在 FastAPI 中自信地處理各種檔案上傳需求,為你的 Web 服務增添完整且安全的功能。祝開發順利!