Django Middleware 中介軟體原理與實作 | Django 教學

2026/06/17 2026/05/22
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功能必要性
SecurityMiddlewareHTTPS 重導向、HSTS 標頭、XSS 保護強烈建議
SessionMiddleware啟用 Session 功能認證的前提
CommonMiddlewareURL 末尾斜線規範化(APPEND_SLASH建議
CsrfViewMiddlewareCSRF 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.pyMIDDLEWARE 列表即可啟用。放置的位置取決於該 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 中處理全域性邏輯的核心機制,掌握它能讓你優雅地處理跨越整個應用程式的共通需求。以下整理本篇的重點:

  1. 洋蔥模型 是 Middleware 的核心概念——Request 由上往下穿透,Response 由下往上返回,形成對稱的包裝結構
  2. 內建 Middleware 各司其職,從安全防護到 Session 管理都有覆蓋,理解它們的功能有助於正確排列順序
  3. MIDDLEWARE 順序至關重要 ,例如 SessionMiddleware 必須在 AuthenticationMiddleware 之前,CORS 必須在最前面
  4. 自訂 Middleware 只需實作 __init____call__ 兩個方法,結構簡潔但功能強大
  5. 三個進階 Hook ——process_viewprocess_exceptionprocess_template_response——讓你在更精確的時間點介入處理流程
  6. 實務應用 涵蓋請求日誌、IP 封鎖、執行時間計算等場景,都是開發中常見的需求

理解 Middleware 後,你對 Django 的請求處理流程會有更全面的認識。下一篇我們將探討 Django 的 Authentication(使用者認證系統) ,學習如何實作登入、登出與使用者管理功能。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。