FastAPI Request Header 與 Cookie 參數

除了 URL 路徑、查詢字串和 Request Body 之外,HTTP 請求中還包含了 Header (標頭) 和 Cookie。

FastAPI 提供了 Header 和 Cookie 類別,用法與 Query 和 Path 非常相似。

Header 參數

要讀取 Header,你需要從 fastapi 匯入 Header。

from typing import Annotated
from fastapi import FastAPI, Header

app = FastAPI()

@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
    return {"User-Agent": user_agent}

自動轉換 (Underscore to Hyphen)

HTTP Header 通常包含連字號 (如 User-Agent),但在 Python 變數命名中我們使用底線 (user_agent)。

預設情況下,Header 會聰明地將參數名稱中的底線 _ 轉換為連字號 - 去讀取對應的 Header。

也就是說,當你宣告 user_agent 時,FastAPI 會去讀取 User-Agent Header。

如果你不想啟用這個自動轉換功能 (例如你的 Header 真的就有底線),可以設定 convert_underscores=False:

@app.get("/items/")
async def read_items(
    strange_header: Optional[str] = Header(None, convert_underscores=False)
):
    return {"strange_header": strange_header}

重複的 Header

如果一個 Header 出現多次 (例如 X-Token: foo 和 X-Token: bar),你可以將型別宣告為 List[str] 來接收所有值。

from typing import List

@app.get("/items/")
async def read_items(x_token: Optional[List[str]] = Header(None)):
    return {"X-Token values": x_token}

讀取 Cookie 的方式與 Header 幾乎一模一樣,只需改用 Cookie 類別。

from typing import Annotated
from fastapi import Cookie, FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
    return {"ads_id": ads_id}
注意:如果你直接宣告 ads_id: str 而沒有使用 Cookie(...) 或 Header(...),FastAPI 會把它當作 Query Parameter。所以務必明確指定。

使用 Pydantic Model 讀取

當你需要同時讀取多個相關的 Header 或 Cookie 時,將它們封裝到一個 Pydantic Model 中會讓程式碼更有結構,也更容易在不同路由之間重用驗證邏輯。

假設你的應用程式有多個追蹤用的 Cookie,你可以這樣定義:

from pydantic import BaseModel, ConfigDict

class CookieModel(BaseModel):
    # 這裡的變數名稱必須跟 Cookie 的名稱完全一致
    session_id: str
    fatebook_tracker: str | None = None
    goog_analytics: str | None = None

    # 建議加上這個設定,防止客戶端傳送多餘的 Cookie 混淆
    model_config = ConfigDict(extra="forbid")

@app.get("/items/")
async def read_items(cookies: Annotated[CookieModel, Cookie()]):
    return cookies

讀取多個 Header

同樣的技巧也能用在 Header 上。這在需要驗證一整組自定義標頭時非常方便:

class CommonHeaders(BaseModel):
    host: str
    save_data: bool
    if_modified_since: str | None = None
    user_agent: str | None = None
    x_token: list[str] = []

    model_config = ConfigDict(extra="forbid")

@app.get("/headers/")
async def read_headers(headers: Annotated[CommonHeaders, Header()]):
    return headers

為什麼要用 Pydantic Model?

  1. 集中管理:驗證規則(如長度、Regex)可以直接寫在 Model 欄位裡。
  2. 重用性:同樣的一組標頭驗證可以輕鬆應用到多個不同的 API 路由。
  3. 自動文件:Swagger UI 會清楚地列出 Model 中定義的所有標頭與 Cookie,並標註哪些是必填的。
  4. 防止多餘資料:透過 extra="forbid",你可以確保 API 嚴格只處理你定義的參數。

總結

  • 使用 Header(...) 讀取 HTTP 標頭。
  • 使用 Cookie(...) 讀取 Cookie。
  • Header 預設會自動將變數名稱的 _ 轉為 -。
  • 兩者的用法與參數驗證功能皆與 Query 和 Path 相同。