FastAPI 課程 – 請求與回應(Request & Response)
主題:自訂回應類型(response_class)
簡介
在使用 FastAPI 建立 API 時,我們常會直接回傳 Python 的資料結構(如 dict、list)或是使用預設的 JSONResponse。雖然這樣足以滿足大多數情境,但在實務開發中,我們經常需要 自訂回應的 Content‑Type、狀態碼、標頭或是回傳格式(例如回傳 HTML、純文字、CSV、檔案流等)。
response_class 參數正是為了這類需求而設計。它讓開發者在 路由宣告階段就指定回應類型,從而避免在函式內手動建立 Response 物件,提升程式的可讀性與維護性。掌握 response_class,不僅能讓 API 更符合前端或第三方系統的期望,也能減少錯誤與重複程式碼。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,一步步帶你深入了解如何在 FastAPI 中 自訂回應類型,並提供實務上常見的應用情境,讓你在日常開發中立即上手。
核心概念
1. 為什麼需要 response_class?
- 明確的 Content‑Type:預設的
JSONResponse會把回傳資料序列化為application/json。如果要回傳 HTML、純文字、XML、CSV 等格式,必須改變Content-Type。 - 自訂狀態碼:有時候回傳的資料本身不代表成功或失敗,需要自行設定 HTTP 狀態碼(如 201、202、204)。
- 額外標頭:例如 CORS、Cache‑Control、Content‑Disposition(下載檔案)等,都可以在回應類型裡一次性設定。
- 效能考量:使用適合的回應類型(例如
StreamingResponse)可以避免一次載入過大資料,減少記憶體佔用。
重點:
response_class只是一個 類別,它會在路由被呼叫時被 FastAPI 自動實例化,並接受路由函式的回傳值作為建構參數。
2. 常見的內建回應類別
| 類別 | 目的 | 預設 Content‑Type |
|---|---|---|
JSONResponse |
回傳 JSON | application/json |
HTMLResponse |
回傳 HTML | text/html |
PlainTextResponse |
回傳純文字 | text/plain |
RedirectResponse |
302 重新導向 | text/plain |
StreamingResponse |
串流傳輸(大檔案、即時資料) | 依 media_type 而定 |
FileResponse |
直接回傳檔案(支援 Range) | 依檔案類型自動偵測 |
Response |
完全自訂(無預設 header) | application/octet-stream |
提示:如果內建類別無法滿足需求,你也可以自行繼承
Response來實作自訂回應類別。
3. 基本語法
from fastapi import FastAPI, Response
from fastapi.responses import HTMLResponse, PlainTextResponse
app = FastAPI()
@app.get("/html", response_class=HTMLResponse)
async def get_html():
return "<h1>Hello, FastAPI!</h1>"
response_class直接寫在@app.get()、@app.post()等裝飾器上。- 函式回傳的資料會被 傳入 指定的回應類別建構子中,FastAPI 會自動完成序列化或編碼的工作。
4. 進階使用 – 搭配 status_code、headers
from fastapi import FastAPI, status
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/create", response_class=JSONResponse, status_code=status.HTTP_201_CREATED)
async def create_item(item: dict):
# 假裝寫入資料庫...
return {"msg": "item created", "item": item}
status_code可在裝飾器參數裡直接指定。- 需要額外標頭時,可在回傳的字典裡加入
headers(或在函式內自行建立Response)。
5. 何時使用 StreamingResponse 與 FileResponse
- 下載檔案:使用
FileResponse能自動支援Content‑Disposition(下載檔名)與 Range 請求,適合大檔案。 - 即時串流(例如 CSV 產生、影片串流):
StreamingResponse允許你以 generator 或 async generator 逐塊傳送資料,降低記憶體壓力。
程式碼範例
下面提供 5 個實用範例,從最簡單的 HTML 回傳到進階的檔案下載與串流,涵蓋常見需求。
範例 1:回傳純文字(PlainTextResponse)
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/ping", response_class=PlainTextResponse)
async def ping():
"""
用於健康檢查的最簡單回應。
客戶端只會收到純文字 "pong"。
"""
return "pong"
說明:
PlainTextResponse會自動設定Content-Type: text/plain; charset=utf-8。- 適合 監控、心跳 或 簡易測試。
範例 2:回傳 HTML(HTMLResponse)並設定自訂標頭
from fastapi import FastAPI, Header
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/welcome", response_class=HTMLResponse)
async def welcome():
"""
回傳一段簡易的 HTML 頁面,並在回應標頭加入自訂資訊。
"""
html_content = """
<html>
<head><title>Welcome</title></head>
<body>
<h1>歡迎使用 FastAPI!</h1>
<p>這是一個自訂 HTML 回應的範例。</p>
</body>
</html>
"""
# 直接在回傳字串外加標頭
return HTMLResponse(content=html_content, headers={"X-Developer": "YourName"})
說明:
- 雖然在裝飾器已指定
HTMLResponse,我們仍可在回傳時手動建立HTMLResponse以加入額外標頭。 - 若不需要額外標頭,只回傳字串即可,FastAPI 會自動使用
HTMLResponse包裝。
範例 3:回傳 JSON 並自訂狀態碼(JSONResponse)
from fastapi import FastAPI, status
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/login", response_class=JSONResponse, status_code=status.HTTP_202_ACCEPTED)
async def login(username: str, password: str):
"""
假設驗證成功,回傳 token,並使用 202 Accepted 表示已接受請求。
"""
# 這裡僅示範,實務請使用安全的驗證機制
token = f"{username}-token"
return {"access_token": token, "token_type": "bearer"}
說明:
status_code直接在裝飾器裡設定,讓回應自動帶入 202。JSONResponse會把回傳的dict序列化為 JSON。
範例 4:檔案下載(FileResponse)
from fastapi import FastAPI
from fastapi.responses import FileResponse
import pathlib
app = FastAPI()
BASE_DIR = pathlib.Path(__file__).parent
@app.get("/download/report", response_class=FileResponse)
async def download_report():
"""
下載一份 CSV 報表。FileResponse 會自動偵測 MIME 類型,
並支援瀏覽器的「另存新檔」對話框。
"""
file_path = BASE_DIR / "reports" / "monthly_report.csv"
# `filename` 參數會設定 Content-Disposition 為 attachment,讓瀏覽器下載
return FileResponse(path=file_path, filename="monthly_report.csv", media_type="text/csv")
說明:
FileResponse只需要提供檔案路徑、下載檔名與media_type。- 若檔案過大,FastAPI 會自動使用 Range 標頭支援斷點續傳。
範例 5:即時串流 CSV(StreamingResponse)
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import csv
import io
app = FastAPI()
def generate_csv():
"""
產生 CSV 的 generator,逐行寫入資料。
這裡以 10,000 筆模擬資料說明。
"""
output = io.StringIO()
writer = csv.writer(output)
# 寫入表頭
writer.writerow(["id", "name", "value"])
yield output.getvalue()
output.seek(0)
output.truncate(0)
for i in range(1, 10001):
writer.writerow([i, f"name_{i}", i * 10])
# 每 1000 筆 flush 一次
if i % 1000 == 0:
yield output.getvalue()
output.seek(0)
output.truncate(0)
# 最後一次 flush
yield output.getvalue()
@app.get("/stream/csv", response_class=StreamingResponse)
async def stream_csv():
"""
直接把 CSV 內容串流給客戶端,適合大量資料下載而不佔用記憶體。
"""
return StreamingResponse(
generate_csv(),
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=big_data.csv"}
)
說明:
generate_csv為 generator,每次產生一小段 CSV 文字,避免一次性載入全部 10,000 筆資料到記憶體。StreamingResponse會把產生的字串逐塊寫入 HTTP 回應。Content-Disposition設為attachment,瀏覽器會自動彈出下載視窗。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 / 最佳實踐 |
|---|---|---|
忘記設定 media_type |
使用 Response 或自訂類別時,若未指定 media_type,預設會是 application/octet-stream,前端可能無法正確解析。 |
始終在回應類別中明確設定 media_type(如 text/html、text/csv)。 |
回傳字串與 Response 混用 |
在已指定 response_class=HTMLResponse 的路由中,直接回傳 Response 物件會導致 FastAPI 再次包裝,產生雙層回應。 |
不要同時使用 response_class 與手動建立同類型的 Response,若需要自訂標頭,直接回傳該類別的實例即可。 |
| 大檔案一次載入 | 使用 FileResponse 時若先把檔案讀入記憶體(open().read()),會失去 Range 支援,且佔用過多記憶體。 |
直接把檔案路徑交給 FileResponse,讓 FastAPI 處理檔案流與 Range。 |
| 錯誤的編碼 | 回傳中文文字或 CSV 時未指定編碼,導致瀏覽器顯示亂碼。 | 在 media_type 加上 charset(text/plain; charset=utf-8)或在 CSV 產生時使用 utf-8-sig。 |
產生過多的 Response 物件 |
在迴圈內每次都建立 Response 會影響效能。 |
一次建立並回傳,或使用 StreamingResponse 逐塊輸出。 |
| 忽略 CORS | 若前端跨域請求回傳 HTML 或檔案,未設定 CORS 會被瀏覽器阻擋。 | 使用 fastapi.middleware.cors.CORSMiddleware,或在回應標頭加入 Access-Control-Allow-Origin。 |
最佳實踐總結
- 先思考回傳類型:是 JSON、HTML、檔案或串流?根據需求直接挑選對應的
response_class。 - 明確設定
media_type與charset,避免前端解析錯誤。 - 盡量使用內建類別(
FileResponse、StreamingResponse),它們已優化了 I/O 與 Header 處理。 - 針對大型資料,使用
StreamingResponse或FileResponse,絕不一次把全部資料讀入記憶體。 - 保持回應簡潔:若只需要額外標頭,直接在
Response建構子裡加headers,不要在路由內多次呼叫Response。
實際應用場景
| 場景 | 使用的 response_class |
為何適合 |
|---|---|---|
| 健康檢查 API | PlainTextResponse |
回傳簡短文字,最小化開銷,易於監控工具解析。 |
| 渲染單頁應用(SPA)的入口 HTML | HTMLResponse |
直接回傳完整的 HTML,讓前端框架接管路由。 |
| OAuth2 登入成功後的跳轉 | RedirectResponse |
302 重新導向到前端授權頁面。 |
| 產生 PDF 報表 | FileResponse |
大檔案下載、支援 Range,讓瀏覽器顯示下載對話框。 |
| 即時匯出大量資料(CSV、Excel) | StreamingResponse |
串流傳輸,降低記憶體使用,適合上百萬筆資料。 |
| 自訂錯誤頁面 | HTMLResponse + status_code=404 |
直接回傳自訂的錯誤 HTML,提升使用者體驗。 |
| WebSocket 之外的 SSE(Server‑Sent Events) | Response(text/event-stream) |
使用 Response 並設定 media_type="text/event-stream",即可推送即時事件。 |
案例說明:
假設你正在開發一個 財務系統,使用者可以點擊「匯出交易紀錄」按鈕,系統會即時產生 CSV 檔案。若直接在端點裡一次性把所有資料讀入記憶體,當資料量達到數十萬筆時,伺服器會發生 MemoryError。此時,你可以改用StreamingResponse搭配生成器,讓 CSV 資料逐行寫入回應,既不佔用過多記憶體,又能讓使用者即時下載檔案。
總結
response_class是 FastAPI 為回應自訂化提供的核心機制,讓開發者能在路由層面直接指定回傳的 Content‑Type、狀態碼與標頭。- 內建的回應類別(
JSONResponse、HTMLResponse、FileResponse、StreamingResponse…)已涵蓋大多數實務需求,若仍不足,可自行繼承Response來實作。 - 正確使用
response_class能提升 API 的可讀性、效能與相容性,尤其在 大檔案下載、即時串流、跨域前端互動 等情境下效果顯著。 - 避免常見陷阱(未設定
media_type、一次載入大檔案、錯誤的編碼等),並遵循最佳實踐:先思考回傳類型 → 明確設定 media_type → 使用內建類別 → 針對大型資料採用串流。
掌握了 response_class,你的 FastAPI 專案將能更靈活地服務前端與第三方系統,從簡單的健康檢查到複雜的檔案串流,都能以最適合的方式回應請求。快把這些技巧運用到你的下個專案,讓 API 更貼近實務需求吧!