本文 AI 產出,尚未審核

FastAPI

單元:請求與回應(Request & Response)

主題:Request 物件(from fastapi import Request


簡介

在 Web 應用程式中,伺服器要能正確取得、解析、驗證客戶端送來的資料,才能完成後續的業務邏輯。FastAPI 內建的 Request 物件正是負責這件事的關鍵工具。它不只是把 HTTP 請求的資訊封裝成一個 Python 物件,更提供了多種便利的屬性與方法,讓開發者可以輕鬆取用標頭、查詢參數、路徑參數、JSON、表單、檔案上傳等資料。

本篇文章將從 概念、使用方式、常見陷阱與最佳實踐,一步步帶領讀者掌握 Request 物件的核心功能,並透過實作範例說明它在實務專案中的應用情境,適合 初學者 想快速上手,也能為 中階開發者 提供更深入的技巧與思考。


核心概念

1. 為什麼需要 Request 物件?

  • 抽象化 HTTP:直接操作 starlette.requests.Request(FastAPI 底層框架)較為繁瑣,FastAPI 直接把它注入到路由函式中,讓我們只需要關注資料本身。
  • 支援非同步Request 支援 async/await,可在非同步路由中安全讀取資料,避免阻塞 I/O。
  • 統一介面:不論是 GETPOSTPUTPATCHDELETE,或是 WebSocket,都能透過相同的屬性取得資訊,降低學習成本。

2. 基本使用方式

在 FastAPI 中,只要在路由函式的參數列表加入 request: Request,框架就會自動把當前的請求實例注入進來:

from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/info")
async def get_info(request: Request):
    # 直接使用 request 物件
    client_host = request.client.host
    return {"client_ip": client_host}

小技巧:如果只需要取得特定資訊(例如標頭),可以直接使用 依賴注入(Dependency Injection)把 Request 包裝成子函式,讓程式碼更具可讀性。

3. 常用屬性與方法

屬性 / 方法 說明 範例
request.method HTTP 方法(GET、POST 等) if request.method == "POST": ...
request.url 完整的 URL 物件,可直接取 pathquery request.url.path
request.headers Headers 物件呈現的所有標頭 request.headers["User-Agent"]
request.query_params 查詢字串(?key=value)的 MultiDict request.query_params.get("page")
await request.json() 解析 JSON 主體(非同步) data = await request.json()
await request.body() 取得原始位元組資料 raw = await request.body()
await request.form() 解析 application/x-www-form-urlencodedmultipart/form-data(表單) form = await request.form()
await request.stream() 以串流方式讀取大型檔案 async for chunk in request.stream(): ...
request.client 客戶端 IP 與埠 request.client.host
request.cookies Cookie 集合 request.cookies.get("session_id")

注意await 只能在 async 定義的路由或依賴函式中使用;若使用同步函式(def),則必須改用 request.json() 等同步版本(FastAPI 會自動轉換),但 建議 盡量使用非同步方式以發揮 FastAPI 的效能優勢。

4. 範例:取得不同來源的資料

以下示範 5 個實務中常見的情境,說明如何透過 Request 取得所需資訊。每段程式碼皆加入詳細註解,方便讀者快速理解。

範例 1️⃣ 讀取 JSON 主體

from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post("/items")
async def create_item(request: Request):
    """
    取得請求的 JSON 主體,並檢查必填欄位。
    """
    try:
        payload = await request.json()          # 解析 JSON
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid JSON")

    # 確認必填欄位
    if "name" not in payload:
        raise HTTPException(status_code=422, detail="Missing 'name' field")
    
    return {"msg": f"Item '{payload['name']}' created"}

範例 2️⃣ 讀取表單資料與檔案上傳

from fastapi import FastAPI, Request, UploadFile, HTTPException

app = FastAPI()

@app.post("/upload")
async def upload_file(request: Request):
    """
    同時取得表單欄位與上傳的檔案。
    前端需使用 multipart/form-data。
    """
    form = await request.form()
    description = form.get("description", "No description")
    upload: UploadFile = form.get("file")      # 取得檔案物件

    if not upload:
        raise HTTPException(status_code=400, detail="File missing")

    # 讀取檔案內容(示範,實務上建議使用流式寫入硬碟)
    content = await upload.read()
    size_kb = len(content) / 1024

    return {
        "filename": upload.filename,
        "size_kb": round(size_kb, 2),
        "description": description,
    }

範例 3️⃣ 取得查詢參數與路徑參數的結合

from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int, request: Request):
    """
    透過 request 直接存取 query string。
    例如:/users/5?detail=full
    """
    detail_level = request.query_params.get("detail", "summary")
    # 假設有一個 get_user_from_db 的函式
    user = {"id": user_id, "name": "Alice"}
    if detail_level == "full":
        user["email"] = "alice@example.com"
        user["roles"] = ["admin", "editor"]
    return user

範例 4️⃣ 讀取原始位元組(例如自訂二進位協定)

from fastapi import FastAPI, Request, Response

app = FastAPI()

@app.post("/binary")
async def binary_endpoint(request: Request):
    """
    接收任意二進位資料,回傳其 SHA256 雜湊。
    """
    raw_body = await request.body()            # 取得原始位元組
    import hashlib
    digest = hashlib.sha256(raw_body).hexdigest()
    return {"sha256": digest}

範例 5️⃣ 取得客戶端資訊與自訂標頭

from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/client-info")
async def client_info(request: Request):
    """
    回傳呼叫端的 IP、User-Agent 與自訂標頭 X-Request-ID。
    """
    client_ip = request.client.host
    user_agent = request.headers.get("User-Agent", "unknown")
    request_id = request.headers.get("X-Request-ID", "none")
    return {
        "client_ip": client_ip,
        "user_agent": user_agent,
        "request_id": request_id,
    }

常見陷阱與最佳實踐

陷阱 說明 解決方式
同步呼叫 await request.json() 在同步路由 (def) 中使用 await 會拋出 SyntaxError 使用 async def,或改用 request.json()(非同步包裝)
重複讀取請求主體 await request.body()await request.json()await request.form() 均會消耗 stream,第二次讀取會得到空值。 先決定一次性讀取方式,或使用 request.state 暫存已解析的資料
忽略大型檔案的記憶體使用 直接 await upload.read() 會一次把整個檔案載入記憶體,對大檔案會造成 OOM。 使用 await upload.read(chunk_size)await request.stream() 逐塊寫入磁碟
未處理缺失標頭或參數 直接 request.headers["X-My-Header"] 若不存在會拋 KeyError 使用 .get() 並提供預設值,或自行檢查 if "X-My-Header" in request.headers
直接信任客戶端 IP 透過 request.client.host 取得的 IP 可能是 proxy(例如 Nginx)後的內部位址。 配合 trusted_hostsX-Forwarded-For 標頭,使用 starlette.middleware.trustedhost.TrustedHostMiddleware

最佳實踐

  1. 統一使用非同步路由:除非有特別需求,盡量以 async def 定義,確保 await request.xxx() 可正常運作。
  2. 一次性解析:若同時需要 JSON 與表單,先判斷 Content-Type 再呼叫對應的解析方法,避免重複讀取。
  3. 使用 request.state 儲存共享資料:在自訂中介層(middleware)裡解析請求後,可把結果放入 request.state,供後續路由共用,減少重複運算。
  4. 妥善驗證與清理:所有從 Request 取得的資料,都應該走 PydanticFastAPI 的依賴驗證,避免直接信任。
  5. 日誌與追蹤:在中介層記錄 request.methodrequest.url.pathrequest.headers(過濾敏感資訊)與 request.client.host,有助於除錯與安全稽核。

實際應用場景

場景 為何需要 Request 典型實作
OAuth2 授權流程 必須讀取 Authorization 標頭、redirect_uri 查詢參數,並在回傳時設定 Set-Cookie 中介層解析 token、驗證後把使用者資訊寫入 request.state.user
Webhooks 接收 第三方服務往往以自訂 JSON 或 XML 格式送來,且可能附帶簽章(Signature)在標頭。 使用 await request.body() 取得原始資料,計算 HMAC 再比對
檔案上傳與即時處理 大檔案需以串流方式讀取,避免一次載入記憶體。 async for chunk in request.stream(): process(chunk)
多租戶 (Multi‑tenant) 系統 Host 或自訂標頭決定租戶,並在每個請求內部設定資料庫連線。 中介層 tenant = request.headers.get("X-Tenant-ID")request.state.db = get_db(tenant)
API 速率限制 (Rate Limiting) 需要取得 client IP、API key 等資訊,計算每分鐘呼叫次數。 中介層讀取 request.client.hostrequest.headers["X-API-Key"],寫入 Redis 計數器

總結

  • Request 物件是 FastAPI 與 Starlette 之間的橋樑,提供了完整且一致的介面,讓開發者能夠輕鬆存取 標頭、查詢參數、路徑參數、JSON、表單、檔案、原始位元組 等資訊。
  • 正確使用 async/await、避免重複讀取主體、妥善驗證與清理,是避免常見錯誤的關鍵。
  • 透過 中介層 + request.state,可以在全局層面統一處理認證、租戶、速率限制等需求,提高程式碼的可維護性與可重用性。
  • 在實務專案中,Request 常被用於 OAuth、Webhooks、檔案上傳、Multi‑tenant 等場景,掌握它的使用技巧將直接提升 API 的彈性與安全性。

**把握 Request 的力量,你的 FastAPI 應用將更具可讀性、可測試性,也更能因應日益複雜的業務需求。**祝你開發順利,持續探索 FastAPI 的無限可能!