FastAPI
單元:請求與回應(Request & Response)
主題:設定自訂 HTTP Headers
簡介
在 Web API 開發中,HTTP Header 是訊息的「隱形」載體,除了攜帶身份驗證資訊、快取指示、內容類型等標準欄位之外,開發者常會自行定義 header 來傳遞額外的上下文資訊(例如版本號、追蹤 ID、語系設定等)。
FastAPI 內建對 Response、Request 物件的完整支援,讓我們能輕鬆在路由函式中讀取或設定自訂 header。掌握這項技巧不僅能提升 API 的可觀測性與彈性,也能在微服務環境下實現跨服務的協調與追蹤。
本篇文章將從 概念說明、實作範例、常見陷阱 與 最佳實踐,一步一步帶你在 FastAPI 中自如地操作自訂 HTTP headers,並提供實務上的應用情境,幫助初學者快速上手,也給中級開發者更多優化的靈感。
核心概念
1. 為什麼需要自訂 Header?
- 跨服務追蹤:在分散式系統中,常會在每一次請求的 header 中加入
X-Request-ID或Trace-Id,讓日誌與追蹤系統能串聯同一筆交易。 - 功能開關:使用
X-Feature-Flag之類的欄位,讓前端或其他服務在不改變 API 本體的情況下,啟用或關閉特定功能。 - 語系與客製化:
X-Locale、X-Client-Version等,可協助後端根據客戶端版本或語系返回不同的內容。
2. FastAPI 中的 Header 兩大入口
| 方向 | 物件 | 主要用途 |
|---|---|---|
| 讀取 | Request(from fastapi import Request) |
取得客戶端送來的所有 header,或指定單一欄位。 |
| 設定 | Response(from fastapi import Response) |
在回傳給客戶端時加入自訂 header,或覆寫既有欄位。 |
Tip:FastAPI 也支援 依賴注入(Dependency Injection) 直接把 header 當作參數注入,適合只需要單一欄位的情況。
3. Header 名稱的慣例
- 標準慣例:使用
X-前綴(已逐步被 IANA 官方的Sec-、Proxy-等取代,但仍廣為使用)。 - 命名規則:使用大寫字母與連字號,如
X-User-Id,FastAPI 會自動把x_user_id轉換回原始名稱。
程式碼範例
以下範例皆以 Python 3.11、FastAPI 0.110 為基礎,使用 uvicorn 作為開發伺服器。
1. 基本的 Request Header 讀取
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.get("/items/")
async def read_items(request: Request):
# 直接從 request.headers 取得所有 header(類似 dict)
user_agent = request.headers.get("user-agent")
if not user_agent:
raise HTTPException(status_code=400, detail="Missing User-Agent header")
return {"User-Agent": user_agent}
說明:
request.headers是 ImmutableHeaders,提供get()、items()等字典方法。- 若 header 名稱大小寫不一致,FastAPI 會自動正規化(
User-Agent、user-agent、USER-AGENT都可取到)。
2. 使用依賴注入直接取得單一 Header
from fastapi import Header, Depends
def get_trace_id(x_trace_id: str = Header(None)):
# 若未傳遞此 header,預設為 None
return x_trace_id
@app.get("/trace/")
async def trace_endpoint(trace_id: str = Depends(get_trace_id)):
return {"trace_id": trace_id or "未提供"}
說明:
Header會自動把x-trace-id轉成x_trace_id參數。- 透過
Depends,此邏輯可在多個路由間重複使用,提升可維護性。
3. 在 Response 中加入自訂 Header
from fastapi import Response
@app.post("/login/")
async def login(username: str, response: Response):
# 假設已完成驗證,產生 JWT token
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
# 設定自訂 header
response.headers["X-Auth-Token"] = token
# 同時也可以設定標準的 Authorization
response.headers["Authorization"] = f"Bearer {token}"
return {"message": "登入成功"}
說明:
Response物件會在路由結束時自動回傳給客戶端,直接操作response.headers即可。- 若同時設定了相同名稱的標準與自訂 header,最後一次賦值會覆寫。
4. 使用 JSONResponse 加入多個 Header
from fastapi.responses import JSONResponse
@app.get("/status/")
async def status():
data = {"service": "order", "status": "healthy"}
headers = {
"X-Service-Version": "v2.3.1",
"Cache-Control": "no-store"
}
return JSONResponse(content=data, headers=headers)
說明:
JSONResponse接受headers參數,可一次性傳入多個欄位。- 這種寫法適合 只回傳 JSON 且不需要額外的
Response物件時使用。
5. 動態產生 Header(例如根據請求內容)
@app.get("/locale/")
async def get_locale(request: Request, response: Response):
# 讀取客戶端傳來的 X-Locale,若無則預設為 zh-TW
client_locale = request.headers.get("x-locale", "zh-TW")
# 根據 locale 設定回傳的 Content-Language
response.headers["Content-Language"] = client_locale
# 同時回傳一個說明訊息
return {"locale_used": client_locale}
說明:
- 這個範例展示 讀取 → 邏輯處理 → 設定 的完整流程。
Content-Language是標準 header,讓瀏覽器或 API 用戶端知道回傳內容的語系。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 大小寫不一致 | 雖然 HTTP header 名稱不區分大小寫,但在程式碼中直接使用字典索引時可能會忘記正規化。 | 使用 request.headers.get("X-My-Header"),或利用 Header 依賴注入,讓 FastAPI 自動處理。 |
| 重複設定同名 Header | 多次賦值會覆寫前面的值,導致意外遺失資訊。 | 若需要 多值(例如 Set-Cookie),請使用 response.headers.append() 或直接建立 MultiDict。 |
| 忘記 CORS 設定 | 客戶端跨域請求時,瀏覽器會過濾自訂 header,導致前端拿不到。 | 在 CORSMiddleware 中加入 expose_headers=["X-Auth-Token", "X-Trace-Id"]。 |
| 安全性洩漏 | 不當地把敏感資訊(如 JWT)放在自訂 header 中,可能被瀏覽器或代理伺服器記錄。 | 僅在 HTTPS 下傳遞,且盡量使用 Authorization 標準欄位。 |
忘記設定 Cache-Control |
自訂 header 可能被快取代理誤快取,造成舊資料回傳。 | 明確設定 Cache-Control: no-store 或適當的快取策略。 |
最佳實踐
- 統一命名規則:所有自訂 header 建議以
X-為前綴,並以大寫加連字號命名。 - 集中管理:將常用的 header 名稱與說明寫在
constants.py,例如HEADER_TRACE_ID = "X-Trace-Id",避免硬編碼。 - 使用依賴注入:對於必須在多個路由中讀取的 header(如
X-Request-ID),建議寫成依賴函式,統一驗證與預設值。 - 加入 CORS
expose_headers:若前端需要讀取自訂 header,務必在CORSMiddleware中明確列出。 - 測試覆寫行為:使用
TestClient撰寫單元測試,確保自訂 header 在不同情境下正確回傳。
from fastapi.testclient import TestClient
client = TestClient(app)
def test_login_header():
resp = client.post("/login/", json={"username": "alice"})
assert "X-Auth-Token" in resp.headers
assert resp.headers["X-Auth-Token"].startswith("eyJ")
實際應用場景
1. 分散式追蹤(Distributed Tracing)
在微服務架構中,每一次 API 呼叫都會攜帶 X-Trace-Id。服務 A 接收到請求後,在回應的 header 中回傳相同的 Trace ID,讓呼叫端(如前端或 API Gateway)可以把整條請求鏈路串起來。
@app.middleware("http")
async def add_trace_id(request: Request, call_next):
trace_id = request.headers.get("X-Trace-Id", str(uuid4()))
response = await call_next(request)
response.headers["X-Trace-Id"] = trace_id
return response
2. 動態功能開關(Feature Flag)
產品團隊想要在特定客戶端先行測試新功能,只需要在請求中加入 X-Feature-Flag: new-dashboard,後端根據此欄位決定回傳的 JSON 結構。
@app.get("/dashboard/")
async def dashboard(x_feature_flag: str = Header(None)):
if x_feature_flag == "new-dashboard":
return {"layout": "new", "widgets": [...]}
return {"layout": "legacy", "widgets": [...]}
3. 多語系內容交付
前端會在每次請求帶上 X-Locale,後端根據此 header 回傳對應語系的文字或日期格式。
@app.get("/greeting/")
async def greeting(request: Request, response: Response):
locale = request.headers.get("X-Locale", "zh-TW")
greetings = {"en-US": "Hello", "zh-TW": "哈囉", "ja-JP": "こんにちは"}
response.headers["Content-Language"] = locale
return {"message": greetings.get(locale, greetings["zh-TW"])}
總結
- 自訂 HTTP header 是 API 與外部系統溝通的靈活管道,能承載追蹤、功能開關、語系等額外資訊。
- 在 FastAPI 中,我們透過
Request.headers讀取、Response.headers設定,或使用 依賴注入(Header)快速取得單一欄位。 - 留意 大小寫正規化、重複設定、CORS 暴露 等常見陷阱,並遵循 命名慣例、集中管理、統一驗證 的最佳實踐。
- 透過範例程式碼與實務情境,我們看到自訂 header 在 分散式追蹤、功能開關、國際化 等場景中的威力。
掌握了這些技巧,你的 FastAPI 專案將更具可觀測性、彈性與可維護性。快把本文的範例套用到自己的服務中,立即感受自訂 header 為 API 帶來的升級吧!