本文 AI 產出,尚未審核

FastAPI 基礎概念:ASGI vs WSGI

簡介

在 Web 框架的世界裡,ASGI(Asynchronous Server Gateway Interface)WSGI(Web Server Gateway Interface) 是兩種最常被提及的介面規範。它們決定了 Python 程式如何與底層的 HTTP 伺服器溝通,直接影響到效能、可擴充性以及開發體驗。
對於想要使用 FastAPI(一個以 ASGI 為核心的現代 API 框架)的開發者而言,了解 ASGI 與 WSGI 的差異不只是理論,而是選擇部署方式、寫程式碼風格、以及未來可否輕鬆加入 WebSocket、背景工作等功能的關鍵。

本文將以 淺顯易懂 的方式說明兩者的概念、實作差異,並提供實用的程式碼範例,協助初學者與中級開發者快速掌握 為什麼 FastAPI 需要 ASGI,以及在什麼情境下仍可能會接觸到 WSGI。


核心概念

1. 什麼是 WSGI?

WSGI 是由 PEP 3333 定義的同步介面,最初設計用來讓 Python Web 應用(如 Django、Flask)與 傳統的 HTTP 伺服器(如 gunicorn、uWSGI)對接。
它的工作流程可以簡化為:

  1. 伺服器 接收到 HTTP 請求,產生一個 environ(字典)與 start_response 回呼函式。
  2. 應用程式 依序處理請求,返回一個可迭代的 bytes 序列作為回應。
  3. 伺服器 把這些 bytes 送回客戶端。

WSGI 完全是 同步 的,也就是說每一次請求都會佔用一條執行緒或工作程序,直到回應完成。

2. 什麼是 ASGI?

ASGI 是在 WSGI 基礎上延伸出來的 非同步 介面(PEP 3333 的後繼者),支援 同步非同步 兩種模式,同時能處理 HTTP、WebSocket、Server‑Sent Events 等多種協定。
其核心概念:

  • Scope:描述連線類型(http、websocket)與相關資訊(method、path、headers)。
  • Receive:非同步函式,用來接收客戶端傳來的事件(如訊息、關閉)。
  • Send:非同步函式,用來向客戶端發送事件(如 http.response.starthttp.response.body)。

ASGI 的設計讓單一執行緒(或單一事件迴圈)即可同時處理大量併發請求,特別適合 I/O 密集(資料庫、外部 API)或 即時通訊(WebSocket)的情境。

3. 同步 vs 非同步:為什麼會影響效能?

項目 WSGI(同步) ASGI(非同步)
請求處理方式 每條執行緒/工作程序一次只能處理一個請求 單一事件迴圈可同時處理多個 I/O 待命的請求
CPU 使用率 高併發時需大量執行緒,CPU 會被切換耗費資源 事件驅動,CPU 只在真正計算時使用
支援協定 僅 HTTP HTTP、WebSocket、SSE 等
典型框架 Flask、Django(傳統) FastAPI、Starlette、Django(ASGI 版)

CPU 密集 的計算任務下,兩者差異不大;但在 I/O 密集(如大量 DB 查詢、外部 API 呼叫)時,ASGI 能顯著降低延遲與資源消耗。

4. FastAPI 為何選擇 ASGI?

FastAPI 的設計目標是 高效能開發友好,同時支援 自動產生 OpenAPI 文件。使用 ASGI 可以:

  • 直接使用 async def 端點,讓開發者寫出 非同步程式碼 而不需額外封裝。
  • 輕鬆加入 WebSocket 路由,打造即時聊天或推播服務。
  • 在同一個應用中混用同步與非同步函式,框架會自動在事件迴圈與執行緒池之間切換。

程式碼範例

以下示範三個常見的比較情境,分別使用 WSGI(Flask)與 ASGI(FastAPI)實作相同的功能,讓你直觀感受差異。

範例 1:單純的 GET API

# Flask (WSGI) - 同步寫法
from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/ping")
def ping():
    # 同步執行,直接回傳
    return jsonify({"msg": "pong"})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)
# FastAPI (ASGI) - 支援 async
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/ping")
async def ping():
    # 即使是 async,也可以寫同步程式碼
    return JSONResponse(content={"msg": "pong"})

重點:在 FastAPI 中即使不需要非同步,仍可使用 async def,框架會自動適配。

範例 2:呼叫外部 API(I/O 密集)

# Flask 同步版(會阻塞整個執行緒)
import requests
from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/weather")
def weather():
    r = requests.get("https://api.example.com/today")
    data = r.json()
    return jsonify(data)
# FastAPI 非同步版(不阻塞事件迴圈)
import httpx
from fastapi import FastAPI

app = FastAPI()

@app.get("/weather")
async def weather():
    async with httpx.AsyncClient() as client:
        r = await client.get("https://api.example.com/today")
        data = r.json()
    return data

使用 httpx.AsyncClientI/O 在等待回應時釋放 CPU,提升併發效能。

範例 3:WebSocket 即時聊天

# Flask (WSGI) 無法原生支援 WebSocket,需要額外套件
# 這裡示範使用 Flask‑SocketIO(仍基於 WSGI)
from flask import Flask
from flask_socketio import SocketIO, emit

app = Flask(__name__)
socketio = SocketIO(app)

@socketio.on("message")
def handle_message(msg):
    emit("reply", {"echo": msg})

if __name__ == "__main__":
    socketio.run(app)
# FastAPI 原生支援 WebSocket(ASGI)
from fastapi import FastAPI, WebSocket

app = FastAPI()

@app.websocket("/ws/chat")
async def chat_socket(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Echo: {data}")

關鍵差異:FastAPI 只需一行程式碼即可開啟 WebSocket,而 Flask 必須依賴額外套件且仍受 WSGI 限制。

範例 4:混用同步與非同步函式

# FastAPI 允許同時存在 sync 與 async
from fastapi import FastAPI
import time

app = FastAPI()

@app.get("/sync")
def sync_endpoint():
    time.sleep(2)          # 阻塞 2 秒
    return {"msg": "sync done"}

@app.get("/async")
async def async_endpoint():
    await asyncio.sleep(2) # 非阻塞 2 秒
    return {"msg": "async done"}

在同一個應用中,FastAPI 會自動把同步端點交給執行緒池,避免阻塞事件迴圈。


常見陷阱與最佳實踐

陷阱 說明 解法
在 ASGI 程式中使用阻塞函式 直接呼叫 requests.get()time.sleep() 會阻塞整個事件迴圈,導致所有連線卡住。 改用 httpx.AsyncClientasyncio.sleep,或將阻塞程式包在 run_in_threadpool 中。
忽略 Scope 的差異 處理 WebSocket 時忘了 await websocket.accept(),導致客戶端連線失敗。 必須先呼叫 accept(),之後才能 receive / send
部署時使用錯誤的伺服器 將 FastAPI 放在 gunicorn(WSGI)而非 uvicorn(ASGI)會失去非同步特性。 使用 uvicornhypercorngunicorn -k uvicorn.workers.UvicornWorker
混用同步 DB 驅動 使用傳統的同步 ORM(如 SQLAlchemy core)在 async 端點中直接呼叫,會阻塞。 使用 SQLModelDatabases、或將同步呼叫包在 run_in_threadpool
忘記設定 max_concurrency 在高併發環境下未限制協程數量,可能導致資源耗盡。 在部署時使用 uvicorn --workers N --limit-concurrency M,或在程式碼中使用 semaphore 控制。

最佳實踐

  1. 盡量使用 async:只要外部 I/O 支援非同步,就使用 async def;同步函式僅保留在需要 CPU 密集或第三方庫不支援 async 時。
  2. 選擇正確的 ASGI 伺服器:開發階段可用 uvicorn,正式環境建議使用 uvicorn + gunicorn(Worker 為 uvicorn.workers.UvicornWorker)。
  3. 統一錯誤處理:利用 FastAPI 的 ExceptionHandler 於全域捕捉異常,避免因未捕捉的阻塞例外導致伺服器崩潰。
  4. 監控與日誌:使用 uvicorn --log-level info 或結合 PrometheusGrafana 監控事件迴圈的延遲與併發數。

實際應用場景

場景 為何選 ASGI(FastAPI) 可能仍會涉及 WSGI
即時聊天、遊戲伺服器 需要 WebSocket 高效能雙向通訊,ASGI 原生支援。 若已有大量傳統 Flask API,仍可保留 WSGI 部分,透過反向代理一起部署。
大量外部 API 呼叫的聚合服務 每筆請求會同時向多個第三方服務發起請求,使用 async 可同時等待,縮短總延遲。 若某些舊有服務只能以同步方式呼叫,可在 FastAPI 中以 run_in_threadpool 包裝。
資料分析平台的批次任務 主要是 CPU 密集,非同步效益較低,仍可使用 FastAPI 的同步端點或傳統 WSGI 框架。 若僅提供報表下載,WSGI 已足夠且部署成本較低。
微服務架構的 API 閘道 需要支援 HTTP/2WebSocket長輪詢,ASGI 最適合。 某些舊有微服務仍是 WSGI,透過 API 閘道統一轉發即可。

總結

  • WSGI 是同步、僅支援 HTTP 的傳統規範,適合簡單、CPU 密集的應用。
  • ASGI 則是 非同步、協定多元 的新一代規範,讓 FastAPI 能夠以 async def 撰寫高效能 API,並原生支援 WebSocket、SSE 等即時功能。
  • FastAPI 專案中,應盡可能使用 非同步 程式碼與 ASGI 伺服器(如 uvicorn),同時注意不要把阻塞函式直接帶入事件迴圈。
  • 了解兩者的差異,能協助你在 部署效能優化、以及 架構選型 時作出正確決策,讓應用既 快速可擴充

掌握 ASGI 與 WSGI 的本質,才能真正發揮 FastAPI 的威力,打造現代化、即時化的 Web 服務。