本文 AI 產出,尚未審核
FastAPI – 表單與檔案上傳(Form & File Upload)
主題:回傳檔案(FileResponse)
簡介
在 Web 開發中,檔案的上傳與下載是最常見的需求之一。FastAPI 除了提供簡潔的表單與檔案接收方式外,還內建了 FileResponse,讓我們可以輕鬆地把伺服器上的檔案回傳給用戶端,支援 內容快取、斷點續傳、正確的 MIME type 等功能。
對於需要提供報表、圖片、PDF、或是使用者自行上傳後再下載的系統,掌握 FileResponse 的使用方式是必備技能。本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你完成 安全、效能佳的檔案回傳。
核心概念
1. 為什麼使用 FileResponse 而不是自行讀檔回傳 Response?
FileResponse會自動設定Content-Type、Content-Disposition,讓瀏覽器正確辨識檔案類型與下載行為。- 它支援
range請求(斷點續傳),適合大檔案的下載需求。 - 內部使用
aiofiles以非同步方式讀檔,減少阻塞,提高效能。
2. 基本使用方式
from fastapi import FastAPI
from fastapi.responses import FileResponse
import os
app = FastAPI()
@app.get("/download/{filename}")
async def download_file(filename: str):
# 設定檔案的完整路徑
file_path = os.path.join("files", filename)
# 若檔案不存在,拋出 404
if not os.path.isfile(file_path):
raise HTTPException(status_code=404, detail="File not found")
# 回傳檔案,讓瀏覽器直接下載
return FileResponse(
path=file_path,
media_type="application/octet-stream",
filename=filename # 下載時的檔名
)
重點:
FileResponse只需要傳入檔案路徑,其他資訊交給它自行處理。
3. 設定 MIME type
若要讓瀏覽器直接預覽(例如圖片、PDF),可以指定正確的 media_type:
return FileResponse(
path=file_path,
media_type="image/png", # 讓瀏覽器直接顯示圖片
filename=filename
)
FastAPI 會根據副檔名自動推斷 media_type,但手動指定可以避免推斷錯誤。
4. 搭配表單上傳後再提供下載
以下示範 上傳檔案、儲存至伺服器,並在上傳成功後回傳檔案的下載連結:
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.responses import JSONResponse
import shutil
import uuid
import os
app = FastAPI()
UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
@app.post("/upload")
async def upload_file(
description: str = Form(...),
file: UploadFile = File(...)
):
# 產生唯一檔名避免衝突
ext = os.path.splitext(file.filename)[1]
unique_name = f"{uuid.uuid4().hex}{ext}"
file_path = os.path.join(UPLOAD_DIR, unique_name)
# 非同步寫入檔案
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
download_url = f"/download/{unique_name}"
return JSONResponse(
content={"msg": "上傳成功", "description": description, "download_url": download_url}
)
註解:
UploadFile內建的file為SpooledTemporaryFile,適合大檔案。- 使用
uuid保證檔名唯一,避免惡意覆寫。
5. 支援斷點續傳(Range Request)
FileResponse 已內建對 Range 標頭的支援,客戶端(如瀏覽器或下載工具)會自動發送 Range: bytes=0-,FastAPI 會回傳 206 Partial Content。開發者只需要確保檔案路徑正確即可。
@app.get("/video/{video_name}")
async def stream_video(video_name: str):
video_path = os.path.join("videos", video_name)
if not os.path.isfile(video_path):
raise HTTPException(status_code=404, detail="Video not found")
return FileResponse(video_path, media_type="video/mp4")
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 檔案路徑硬編碼 | 直接寫死路徑容易在不同環境(dev / prod)出錯。 | 使用環境變數或 Pathlib 產生相對路徑。 |
| 未驗證檔名 | 直接使用使用者提供的檔名會產生路徑穿越(../../)風險。 |
只接受白名單副檔名,或使用 uuid 產生安全檔名。 |
| 同步 I/O | 使用 open(..., "rb") 讀檔會阻塞事件迴圈。 |
交給 FileResponse(已使用非同步)或自行使用 aiofiles。 |
忘記設定 Content-Disposition |
瀏覽器預設會直接顯示(例如圖片),但有時需要強制下載。 | 在 FileResponse 中傳入 filename=,FastAPI 會自動加上 attachment; filename=。 |
| 大檔案未設定適當緩衝 | 若一次性讀取整個檔案會佔用過多記憶體。 | 交給 FileResponse 或自行實作 Chunked Streaming。 |
最佳實踐要點
- 使用
Path(pathlib)管理路徑:from pathlib import Path BASE_DIR = Path(__file__).parent UPLOAD_DIR = BASE_DIR / "uploads" - 限制檔案大小:在
UploadFile接收前,使用 Middleware 或自訂依賴檢查Content-Length。 - 設定快取標頭(若檔案不常變更):
return FileResponse( path=file_path, media_type="application/pdf", headers={"Cache-Control": "public, max-age=86400"} ) - 日誌與例外處理:記錄檔案下載成功/失敗,並返回統一的錯誤訊息。
實際應用場景
| 場景 | 需求 | FileResponse 的角色 |
|---|---|---|
| 線上報表系統 | 使用者點擊「匯出 Excel」產生報表,需即時下載。 | 產生臨時 Excel 檔後,用 FileResponse 回傳,設定 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet。 |
| 影音串流平台 | 影片播放需要支援斷點續傳與快取。 | 直接回傳 MP4 檔,FileResponse 自動處理 Range,讓前端播放器順暢播放。 |
| 文件管理系統 | 使用者上傳 PDF 後,點擊檔名即在新分頁預覽。 | 設定 media_type="application/pdf",瀏覽器會直接顯示 PDF。 |
| 教育平台 | 老師上傳課程教材(ZIP),學生下載。 | 使用 filename= 讓下載時保留原檔名,並加上 Cache-Control 以減少重複下載。 |
| API 下載服務 | 第三方系統以程式方式取得 CSV 檔。 | 回傳 Content-Type: text/csv,並可在 Header 中加入 Content-Disposition: attachment; filename="data.csv"。 |
總結
FileResponse是 FastAPI 提供的高效檔案回傳工具,自動處理 MIME、快取、斷點續傳等細節。- 使用時只要確保 檔案路徑安全、正確設定
media_type與filename,即可讓瀏覽器或客戶端得到預期的下載或預覽行為。 - 配合 表單上傳、UUID 命名、Pathlib 等最佳實踐,能夠建構出 安全、可維護且效能佳 的檔案服務。
掌握了以上概念與範例後,你就能在 FastAPI 專案中輕鬆實作各式檔案上傳、儲存與回傳的功能,為使用者提供流暢且可靠的檔案體驗。祝開發順利!