本文 AI 產出,尚未審核

FastAPI 教學:請求與回應 – RedirectResponseFileResponseStreamingResponse

簡介

在 Web 開發中,回應(Response) 不僅僅是傳回 JSON 資料那麼簡單。
有時候我們需要將使用者導向另一個 URL(Redirect),或是直接下載檔案(File),甚至把大型檔案或即時串流資料以 流式 方式傳送給前端。FastAPI 內建了三個非常好用的回應類別:

  • RedirectResponse – 讓瀏覽器自動跳轉到指定網址
  • FileResponse – 直接回傳本機檔案,支援 Content‑Disposition、範圍請求(range)等
  • StreamingResponse – 以 generator 或 async generator 方式逐塊傳送資料,適合大檔案、影片、即時資料等情境

掌握這三個回應類別,能讓你的 API 更具彈性,也能提升使用者體驗與效能。本文將從概念說明、實作範例、常見陷阱與最佳實踐,最後以實務情境帶你快速上手。


核心概念

1. RedirectResponse

RedirectResponse 繼承自 Response,會在 HTTP 標頭加入 Location,並預設使用 302 Found(臨時重新導向)。如果要做永久導向(301)或其他狀態碼,只要在建構子裡自行指定即可。

為什麼需要 Redirect?

  • 使用者完成登入後,導向原本想去的頁面
  • API 變更路徑,舊網址仍然保留,以免斷鏈
  • OAuth2、第三方授權流程中,常會需要跳轉回 callback URL

基本範例

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()

@app.get("/old-path")
def old_path():
    """
    舊的 API 路徑,現在改到 /new-path。
    使用 301 永久導向,讓搜尋引擎與瀏覽器快取新位置。
    """
    return RedirectResponse(url="/new-path", status_code=301)

@app.get("/new-path")
def new_path():
    return {"message": "已成功導向至新路徑!"}

小技巧:若要保留原始查詢參數,可使用 request.url_for 並手動拼接 query_params


2. FileResponse

FileResponse 用於 直接回傳檔案,它會自動設定 Content-TypeContent-Length,並支援 斷點續傳(Range)與 下載檔名Content-Disposition)的設定。非常適合提供 PDF、圖片、CSV、ZIP 等檔案下載功能。

重要參數說明

參數 說明
path 檔案在伺服器的絕對或相對路徑
media_type 回傳的 MIME type,預設會根據檔案副檔名偵測
filename 若提供,會在 Content-Disposition 中加入 attachment; filename="...",讓瀏覽器下載而不是直接開啟
headers 其他自訂標頭,例如 Cache-Control
background 完成回傳後要執行的背景任務(例如刪除暫存檔)

基本範例:提供下載 PDF

from fastapi import FastAPI
from fastapi.responses import FileResponse
import pathlib

app = FastAPI()
BASE_DIR = pathlib.Path(__file__).parent

@app.get("/download/report")
def download_report():
    """
    下載年度報告(PDF),檔名會顯示為 report_2024.pdf
    """
    file_path = BASE_DIR / "static" / "report_2024.pdf"
    return FileResponse(
        path=str(file_path),
        media_type="application/pdf",
        filename="report_2024.pdf",
        headers={"Cache-Control": "no-cache"},
    )

範例:支援斷點續傳的大檔案

@app.get("/download/large-video")
def download_video():
    """
    大型影片檔案,讓瀏覽器可以使用「快取」或「續傳」功能。
    """
    video_path = BASE_DIR / "videos" / "movie_4k.mp4"
    return FileResponse(
        path=str(video_path),
        media_type="video/mp4",
        filename="movie_4k.mp4",
        # FastAPI 內建支援 Range,無需額外設定
    )

3. StreamingResponse

StreamingResponse 允許 逐塊(chunk)傳送 資料,適用於:

  • 大檔案的即時下載(不想一次載入記憶體)
  • 影片/音訊串流(HLS、MPEG‑DASH)
  • 產生 CSV、Excel 等動態檔案
  • 伺服器端事件(Server‑Sent Events, SSE)

兩種常見產生方式

  1. 同步 generator:適合 CPU‑bound、簡單的資料產生
  2. 非同步 async generator:適合 I/O‑bound(如讀檔、呼叫外部 API)

範例 1:同步 generator 產生 CSV

import csv
from io import StringIO
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

def csv_generator():
    """
    產生 1000 行測試資料的 CSV,每次 yield 一行字串。
    """
    buffer = StringIO()
    writer = csv.writer(buffer)
    writer.writerow(["id", "name", "score"])
    yield buffer.getvalue()
    buffer.seek(0)
    buffer.truncate(0)  # 清空緩衝區

    for i in range(1, 1001):
        writer.writerow([i, f"User{i}", i % 100])
        yield buffer.getvalue()
        buffer.seek(0)
        buffer.truncate(0)

@app.get("/stream/csv")
def stream_csv():
    """
    透過 StreamingResponse 直接把 CSV 串流回給前端,
    瀏覽器會自動觸發下載。
    """
    return StreamingResponse(
        csv_generator(),
        media_type="text/csv",
        headers={"Content-Disposition": "attachment; filename=users.csv"},
    )

範例 2:非同步 generator 讀取大檔案

import aiofiles
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

async def file_chunk_reader(file_path: str, chunk_size: int = 1024 * 1024):
    """
    非同步讀取檔案,每次傳回 chunk_size 大小的位元組。
    """
    async with aiofiles.open(file_path, mode="rb") as f:
        while True:
            chunk = await f.read(chunk_size)
            if not chunk:
                break
            yield chunk

@app.get("/stream/large-log")
async def stream_large_log():
    """
    讓使用者即時下載巨大的 log 檔(例如 5 GB),
    只佔用極少的記憶體。
    """
    log_path = "logs/2024-11-20.log"
    return StreamingResponse(
        file_chunk_reader(log_path),
        media_type="text/plain",
        headers={"Content-Disposition": "attachment; filename=2024-11-20.log"},
    )

範例 3:Server‑Sent Events(SSE)實作

import asyncio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

async def event_generator():
    """
    每隔 2 秒推送一次訊息給前端,適合作為即時監控或聊天室。
    """
    counter = 0
    while True:
        await asyncio.sleep(2)
        counter += 1
        yield f"data: 訊息 {counter}\\n\\n"

@app.get("/sse/heartbeat")
def sse_heartbeat():
    """
    SSE 端點,前端可以使用 EventSource 連結。
    """
    return StreamingResponse(event_generator(), media_type="text/event-stream")

常見陷阱與最佳實踐

陷阱 說明 解決方案 / 最佳實踐
忘記設定 filename FileResponse 若未提供 filename,瀏覽器會直接在網址列顯示檔案路徑,使用者體驗差。 使用 filename= 參數,或自行在 headersContent‑Disposition
大檔案一次載入記憶體 直接回傳 Response(content=bytes) 會把整個檔案讀入記憶體,容易 OOM。 改用 FileResponse(內建分段傳輸)或 StreamingResponse 搭配 generator。
Range 請求未支援 若自行實作檔案串流,忘記處理 Range 標頭會導致斷點續傳失效。 FileResponse 自動支援;若自行寫 StreamingResponse,要自行解析 Range
同步 I/O 阻塞事件迴圈 在 async 路由裡使用同步 open()read() 會阻塞事件迴圈。 使用 aiofiles 或其他非同步 I/O 庫;或把阻塞操作搬到執行緒池(run_in_threadpool)。
忘記設定 Cache-Control 靜態檔案或下載檔案若未設定快取策略,會造成不必要的重複下載。 依需求加入 Cache-Control: public, max-age=86400no-cache
未處理例外 檔案不存在或權限不足時會拋出 FileNotFoundError,回傳 500。 使用 try/except 包住回傳,或在 FastAPI 中加入自訂例外處理器。

最佳實踐小結

  1. 盡量使用內建 ResponseFileResponseRedirectResponse)來減少自行處理 HTTP 標頭的錯誤機率。
  2. 大檔案或即時串流:優先考慮 StreamingResponse + async generator。
  3. 安全性:永遠驗證使用者是否有權限讀取或下載檔案,避免路徑穿越(path traversal)攻擊。
  4. 測試:使用 TestClientfastapi.testclient.TestClient)驗證 LocationContent‑DispositionRange 等標頭是否正確。

實際應用場景

場景 使用哪個 Response 為什麼適合
使用者登入成功後導回原本頁面 RedirectResponse(302/301) 簡潔且符合 HTTP 標準,前端不需額外處理
產出報表(PDF、Excel)讓使用者下載 FileResponse 自動偵測 MIME、支援斷點續傳,省去手動寫標頭
大型影片串流(支援快轉、暫停) FileResponse + Range 影片播放器會自行發送 Range,FastAPI 內建支援
即時產生 CSV(依照使用者條件) StreamingResponse(同步 generator) 只在產生時寫入,不佔用大量記憶體
監控系統即時推送訊息給前端儀表板 StreamingResponse(SSE) 低延遲、瀏覽器原生支援 EventSource
大型日志檔案供客服下載分析 StreamingResponse(async generator) 直接從磁碟分塊讀取,避免 OOM,且支援斷點續傳(自行實作)

總結

  • RedirectResponse:簡化導向流程,記得根據需求選擇 301(永久)或 302(臨時)狀態碼。
  • FileResponse:下載靜態檔案或大檔案的首選,內建 MIME 判斷、斷點續傳與快取控制。
  • StreamingResponse:處理動態產生或巨量資料時的利器,配合同步或非同步 generator,可實作 CSV、影片串流、SSE 等多種應用。

掌握這三種回應類別,你就能在 FastAPI 中自由組合 導向下載即時串流 三大功能,寫出既高效又符合使用者期待的 API。趕快把範例搬到自己的專案中,體驗從「回傳 JSON」到「完整 Web 互動」的升級吧!祝開發順利 🎉