Django Middleware 中介軟體原理與實作 | Django 教學
在 Django 的 Request/Response 處理流程中, Middleware(中介軟體) 扮演著全域攔截層的角色。每一個進入 Django 的請求,都會依序經過所有 Middleware 的處理,最後才到達 View ;而 View 產生的回應,又會反向經過這些 Middleware 後才送回給使用者。這種設計被稱為 洋蔥模型(Onion Model) ,是理解 Django 架構的關鍵之一。本篇將帶你從原理到實作,完整掌握 Middleware 的運作機制。
Middleware 概念與洋蔥模型
Middleware(中介軟體) 是一個輕量的「外掛」系統,它可以在 Django 處理 Request 和 Response 的過程中插入自訂邏輯。你可以把它想像成一顆洋蔥——Request 從最外層往內逐層穿透,直到核心的 View 執行完畢,Response 再從內層往外逐層返回。
Request → [Middleware 1] → [Middleware 2] → [Middleware N] → View
Response ← [Middleware 1] ← [Middleware 2] ← [Middleware N] ← View
這個模型有兩個關鍵特性:
- Request 階段:從
MIDDLEWARE列表 由上往下 執行(index 0 到 N) - Response 階段:從 View 返回後 由下往上 執行(index N 到 0)
也就是說,第一個 Middleware 最先接觸到 Request,也最後處理 Response,形成對稱的包裝結構。
Django 內建 Middleware 介紹
Django 預設提供了多個實用的 Middleware,每個都負責特定的功能。以下是常見的內建 Middleware 及其用途:
| Middleware | 功能 | 必要性 |
|---|---|---|
SecurityMiddleware | HTTPS 重導向、HSTS 標頭、XSS 保護 | 強烈建議 |
SessionMiddleware | 啟用 Session 功能 | 認證的前提 |
CommonMiddleware | URL 末尾斜線規範化(APPEND_SLASH) | 建議 |
CsrfViewMiddleware | CSRF token 驗證 | 強烈建議 |
AuthenticationMiddleware | 將使用者物件掛到 request.user | 需要 Session |
MessageMiddleware | 啟用 Django 訊息框架 | 可選 |
XFrameOptionsMiddleware | 設定 X-Frame-Options 防止點擊劫持 | 可選 |
預設的 settings.py 設定如下:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
MIDDLEWARE 順序的重要性
Middleware 之間存在依賴關係,排列順序至關重要。順序錯誤可能導致功能異常甚至安全漏洞。
常見的順序錯誤
錯誤:SessionMiddleware 放在 AuthenticationMiddleware 之後
# 錯誤:AuthenticationMiddleware 無法讀取 Session
MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', # 需要 Session,但 Session 還沒載入
'django.contrib.sessions.middleware.SessionMiddleware', # 應該在上面
]
# 正確:SessionMiddleware 必須在 AuthenticationMiddleware 之前
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', # 先載入 Session
'django.contrib.auth.middleware.AuthenticationMiddleware', # 才能讀取使用者資訊
]
錯誤:CORS Middleware 位置太後面
如果使用 django-cors-headers 套件,CORS Middleware 必須放在最前面,否則 Preflight 請求(預檢請求) 可能被 CSRF 等 Middleware 攔截:
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # CORS 必須在最前面
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# ...其他 Middleware
]
自訂 Middleware 撰寫
Django 1.10 以後推薦使用新式 Middleware,它是一個 callable class,只需實作 __init__ 和 __call__ 兩個方法。
基本結構
class SimpleMiddleware:
def __init__(self, get_response):
# 只在 Django 啟動時執行一次
# 適合做一次性的初始化設定
self.get_response = get_response
def __call__(self, request):
# ——— Request 處理階段(View 執行前)———
# 在這裡對 request 做前處理
response = self.get_response(request) # 呼叫下一層,最終到 View
# ——— Response 處理階段(View 執行後)———
# 在這裡對 response 做後處理
return response
__init__(self, get_response):在 Django 啟動時呼叫一次。get_response是一個 callable,代表下一個 Middleware 或最終的 View__call__(self, request):每個 Request 進來時都會呼叫。在self.get_response(request)前後分別是 Request 階段和 Response 階段
實務範例一:請求日誌 Middleware
記錄每個請求的方法、路徑和狀態碼,是最常見的自訂 Middleware 之一:
# myapp/middleware.py
import logging
logger = logging.getLogger(__name__)
class RequestLoggingMiddleware:
"""記錄每個 HTTP 請求的基本資訊"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 記錄請求資訊
logger.info(
f"收到請求:{request.method} {request.path} "
f"| IP: {self.get_client_ip(request)}"
)
response = self.get_response(request)
# 記錄回應狀態
logger.info(
f"回應完成:{request.method} {request.path} "
f"→ {response.status_code}"
)
return response
@staticmethod
def get_client_ip(request):
"""取得客戶端 IP,考慮反向代理情境"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
return x_forwarded_for.split(',')[0].strip()
return request.META.get('REMOTE_ADDR')
實務範例二:IP 封鎖 Middleware
根據黑名單阻擋特定 IP 的請求,在 Request 階段直接返回 403 回應,不讓請求到達 View:
from django.http import HttpResponseForbidden
from django.conf import settings
class IPBlockMiddleware:
"""封鎖黑名單中的 IP 位址"""
def __init__(self, get_response):
self.get_response = get_response
# 從 settings 載入黑名單,只執行一次
self.blocked_ips = getattr(settings, 'BLOCKED_IPS', [])
def __call__(self, request):
client_ip = self.get_client_ip(request)
# 如果 IP 在黑名單中,直接返回 403
if client_ip in self.blocked_ips:
return HttpResponseForbidden(
f"您的 IP ({client_ip}) 已被封鎖。"
)
return self.get_response(request)
@staticmethod
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
return x_forwarded_for.split(',')[0].strip()
return request.META.get('REMOTE_ADDR')
在 settings.py 中設定黑名單與註冊 Middleware:
# settings.py
BLOCKED_IPS = ['192.168.1.100', '10.0.0.50']
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'myapp.middleware.IPBlockMiddleware', # 放在前面,儘早攔截
# ...其他 Middleware
]
實務範例三:執行時間計算 Middleware
測量每個請求的處理時間,並將結果寫入 Response Header 和日誌:
import time
import logging
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""計算每個請求的處理時間"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time
# 將執行時間寫入 Response Header
response['X-Request-Duration'] = f"{duration:.3f}s"
# 記錄到日誌,超過 1 秒的請求標記為警告
log_message = (
f"{request.method} {request.path} "
f"→ {response.status_code} [{duration:.3f}s]"
)
if duration > 1.0:
logger.warning(f"慢速請求:{log_message}")
else:
logger.info(log_message)
return response
進階 Hook 方法
除了 __call__ 之外,Django Middleware 還提供三個可選的 Hook(鉤子) 方法,讓你在更精確的時間點插入邏輯。
process_view()
在 Django 確定要呼叫哪個 View 之後、但在實際執行 View 之前觸發。你可以透過這個 hook 檢查 View 函式的屬性或參數:
class ViewInspectMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
"""
參數說明:
- view_func:即將執行的 View 函式
- view_args:傳給 View 的位置參數
- view_kwargs:傳給 View 的關鍵字參數
回傳值:
- None:繼續正常執行 View
- HttpResponse:短路,直接返回此 response
"""
# 範例:檢查 View 是否需要 Premium 會員
if getattr(view_func, 'requires_premium', False):
if not getattr(request.user, 'is_premium', False):
from django.http import HttpResponseForbidden
return HttpResponseForbidden("此功能需要 Premium 會員")
return None
process_exception()
當 View 拋出未捕獲的例外時觸發,是集中處理錯誤的好地方:
import logging
from django.http import JsonResponse
logger = logging.getLogger(__name__)
class ExceptionHandlerMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
"""
當 View 拋出例外時執行
回傳值:
- None:交給下一個 Middleware 的 process_exception 處理
- HttpResponse:使用此 response 作為最終回應
"""
logger.error(
f"未捕獲例外:{request.path}",
exc_info=exception
)
# 針對特定例外類型返回自訂回應
if isinstance(exception, PermissionError):
return JsonResponse(
{'error': '權限不足'},
status=403
)
# 回傳 None,讓 Django 預設錯誤處理機制接手
return None
process_template_response()
當 View 返回的是 TemplateResponse(而非普通的 HttpResponse)時觸發。此時模板尚未渲染,你可以在渲染前注入額外的 Context(模板上下文) 資料:
class TemplateContextMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_template_response(self, request, response):
"""在 TemplateResponse 渲染前注入全域 context"""
if hasattr(response, 'context_data') and response.context_data is not None:
response.context_data['site_name'] = 'My Site'
response.context_data['current_year'] = 2026
return response # 必須回傳 response
Middleware 的適用場景
了解何時該用 Middleware、何時不該用,是寫出好架構的關鍵。
適合使用 Middleware 的情境:
- 全域性的 Request/Response 攔截(日誌、計時、追蹤)
- 全站認證與授權前置檢查
- 跨域(CORS)標頭注入
- 全域錯誤處理與格式化
- 維護模式開關
不適合使用 Middleware 的情境:
- 只針對特定 View 的邏輯(應使用 裝飾器(Decorator) 或 Mixin)
- 業務邏輯處理(應放在 View 或 Service 層)
- 只影響少數路由的操作(Middleware 會對所有請求執行)
註冊自訂 Middleware
將自訂 Middleware 加入 settings.py 的 MIDDLEWARE 列表即可啟用。放置的位置取決於該 Middleware 的功能需求:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'myapp.middleware.IPBlockMiddleware', # 安全相關,放前面
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'myapp.middleware.RequestLoggingMiddleware', # 日誌記錄
'myapp.middleware.RequestTimingMiddleware', # 執行時間計算
]
總結
Middleware 是 Django 中處理全域性邏輯的核心機制,掌握它能讓你優雅地處理跨越整個應用程式的共通需求。以下整理本篇的重點:
- 洋蔥模型 是 Middleware 的核心概念——Request 由上往下穿透,Response 由下往上返回,形成對稱的包裝結構
- 內建 Middleware 各司其職,從安全防護到 Session 管理都有覆蓋,理解它們的功能有助於正確排列順序
- MIDDLEWARE 順序至關重要 ,例如
SessionMiddleware必須在AuthenticationMiddleware之前,CORS 必須在最前面 - 自訂 Middleware 只需實作
__init__和__call__兩個方法,結構簡潔但功能強大 - 三個進階 Hook ——
process_view、process_exception、process_template_response——讓你在更精確的時間點介入處理流程 - 實務應用 涵蓋請求日誌、IP 封鎖、執行時間計算等場景,都是開發中常見的需求
理解 Middleware 後,你對 Django 的請求處理流程會有更全面的認識。下一篇我們將探討 Django 的 Authentication(使用者認證系統) ,學習如何實作登入、登出與使用者管理功能。