中介層

中介層是一個進入 Django 請求/回應處理流程的掛鉤框架。它是一個輕量級、低階的「外掛」系統,用於全域地更改 Django 的輸入或輸出。

每個中介層組件負責執行某些特定功能。例如,Django 包含一個中介層組件,AuthenticationMiddleware,它使用會話將使用者與請求關聯起來。

本文檔說明中介層如何運作、如何啟動中介層以及如何編寫自己的中介層。Django 隨附一些內建的中介層,您可以直接使用。它們記錄在內建中介層參考中。

編寫您自己的中介層

中介層工廠是一個可呼叫的物件,它接收一個 get_response 可呼叫物件並返回一個中介層。中介層是一個可呼叫的物件,它接收一個請求並返回一個回應,就像視圖一樣。

中介層可以編寫為如下所示的函式

def simple_middleware(get_response):
    # One-time configuration and initialization.

    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

    return middleware

或者,它可以編寫為其實例可呼叫的類別,如下所示

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

Django 提供的 get_response 可呼叫物件可能是實際的視圖(如果這是最後一個列出的中介層),或者它可能是鏈中的下一個中介層。目前的中介層不需要知道或關心它到底是什麼,只需知道它代表接下來的任何內容即可。

上述內容是一個略微簡化的版本 – 鏈中最後一個中介層的 get_response 可呼叫物件不是實際的視圖,而是處理常式中的包裝方法,該方法負責應用視圖中介層、使用適當的 URL 引數呼叫視圖,並應用範本回應例外中介層。

中介層可以僅支援同步 Python(預設)、僅支援非同步 Python 或兩者都支援。請參閱非同步支援,了解如何宣告您支援的內容,以及了解您接收的是哪種類型的請求。

中介層可以存在於您的 Python 路徑上的任何位置。

__init__(get_response)

中介層工廠必須接受 get_response 引數。您也可以初始化中介層的一些全域狀態。請記住以下幾個注意事項

  • Django 僅使用 get_response 引數初始化您的中介層,因此您不能定義 __init__() 作為要求任何其他引數。

  • 與每個請求呼叫一次的 __call__() 方法不同,__init__() 僅在 Web 伺服器啟動時呼叫一次

將中介層標記為未使用

有時,在啟動時確定是否應使用某個中介層會很有用。在這些情況下,您的中介層的 __init__() 方法可能會引發 MiddlewareNotUsed。然後,Django 會從中介層處理中移除該中介層,並在 DEBUGTrue 時,將偵錯訊息記錄到 django.request 記錄器中。

啟動中介層

若要啟動中介層組件,請將其新增至 Django 設定中的 MIDDLEWARE 清單中。

MIDDLEWARE 中,每個中介層組件都由一個字串表示:中介層工廠的類別或函數名稱的完整 Python 路徑。例如,這是由 django-admin startproject 建立的預設值

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",
]

Django 安裝不需要任何中介層 — 如果您願意,MIDDLEWARE 可以為空 — 但強烈建議您至少使用 CommonMiddleware

MIDDLEWARE 中的順序很重要,因為中介層可以依賴其他中介層。例如,AuthenticationMiddleware 會將已驗證的使用者儲存在會話中;因此,它必須在 SessionMiddleware 之後執行。請參閱中介層排序,了解有關 Django 中介層類別排序的一些常見提示。

中介層順序和分層

在呼叫視圖之前的請求階段,Django 會按照 MIDDLEWARE 中定義的順序(由上而下)套用中介層。

您可以將其想像成一個洋蔥:每個中介層類別都是一個「層」,它包裝了視圖,而視圖位於洋蔥的核心。如果請求通過洋蔥的所有層(每一層都會呼叫 get_response 以將請求傳遞到下一層),一直到核心的視圖,則回應將在返回時通過每一層(以相反的順序)。

如果其中一個層決定短路並返回回應,而沒有呼叫其 get_response,則該層內部的任何洋蔥層(包括視圖)都不會看到請求或回應。回應只會通過請求傳入的相同層返回。

其他中介層掛鉤

除了前面描述的基本請求/回應中介層模式之外,您還可以將其他三個特殊方法新增至基於類別的中介層

process_view()

process_view(request, view_func, view_args, view_kwargs)

request 是一個 HttpRequest 物件。view_func 是 Django 即將使用的 Python 函式。(它是實際的函式物件,而不是函式名稱的字串。)view_args 是將傳遞給視圖的位置引數清單,而 view_kwargs 是將傳遞給視圖的關鍵字引數字典。view_argsview_kwargs 都不包括第一個視圖引數 (request)。

在 Django 呼叫視圖之前,會先呼叫 process_view()

它應該返回 None 或一個 HttpResponse 物件。如果它返回 None,Django 將繼續處理此請求,執行任何其他 process_view() 中介層,然後執行適當的視圖。如果它返回一個 HttpResponse 物件,Django 將不會費心呼叫適當的視圖;它會將回應中介層套用至該 HttpResponse,並返回結果。

注意

在視圖執行之前或在 process_view() 中存取中介層內的 request.POST 會防止在中介層之後執行的任何視圖能夠動態修改請求的檔案上傳處理常式,並且通常應避免這種情況。

if response.streaming:
    response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
    response.content = alter_content(response.content)

注意

def wrap_streaming_content(content):
    for chunk in content:
        yield alter_content(chunk)

from asgiref.sync import iscoroutinefunction
from django.utils.decorators import sync_and_async_middleware


@sync_and_async_middleware
def simple_middleware(get_response):
    # One-time configuration and initialization goes here.
    if iscoroutinefunction(get_response):

        async def middleware(request):
            # Do something here!
            response = await get_response(request)
            return response

    else:

        def middleware(request):
            # Do something here!
            response = get_response(request)
            return response

    return middleware

注意

from asgiref.sync import iscoroutinefunction, markcoroutinefunction


class AsyncMiddleware:
    async_capable = True
    sync_capable = False

    def __init__(self, get_response):
        self.get_response = get_response
        if iscoroutinefunction(self.get_response):
            markcoroutinefunction(self)

    async def __call__(self, request):
        response = await self.get_response(request)
        # Some logic ...
        return response

class django.utils.deprecation.MiddlewareMixin

Django 提供了 django.utils.deprecation.MiddlewareMixin,以簡化建立與 MIDDLEWARE 和舊的 MIDDLEWARE_CLASSES 皆相容的中介層類別,並支援同步和非同步請求。所有 Django 內建的中介層類別都與這兩個設定相容。

該 mixin 提供了一個 __init__() 方法,需要一個 get_response 參數,並將其儲存在 self.get_response 中。

__call__() 方法會:

  1. 呼叫 self.process_request(request)(如果已定義)。

  2. 呼叫 self.get_response(request) 以從後續的中介層和視圖取得回應。

  3. 呼叫 self.process_response(request, response)(如果已定義)。

  4. 回傳回應。

如果與 MIDDLEWARE_CLASSES 一起使用,則永遠不會使用 __call__() 方法;Django 會直接呼叫 process_request()process_response()

在大多數情況下,繼承這個 mixin 就足以使舊式中介層與新系統相容,並具有足夠的向後相容性。新的短路語意對於現有的中介層是無害的,甚至是有益的。在少數情況下,中介層類別可能需要一些變更才能適應新的語意。

以下是使用 MIDDLEWAREMIDDLEWARE_CLASSES 之間的行為差異

  1. MIDDLEWARE_CLASSES 下,每個中介層都會始終呼叫其 process_response 方法,即使較早的中介層透過從其 process_request 方法回傳回應而短路。在 MIDDLEWARE 下,中介層的行為更像洋蔥:回應在往外傳遞時經過的層級,與請求在往內傳遞時經過的層級相同。如果中介層短路,則只有該中介層和在 MIDDLEWARE 中位於其之前的層級會看到回應。

  2. MIDDLEWARE_CLASSES 下,process_exception 適用於從中介層 process_request 方法引發的例外。在 MIDDLEWARE 下,process_exception 僅適用於從視圖引發的例外(或從 TemplateResponserender 方法)。從中介層引發的例外會轉換為適當的 HTTP 回應,然後傳遞給下一個中介層。

  3. MIDDLEWARE_CLASSES 下,如果 process_response 方法引發例外,則會跳過所有較早的中介層的 process_response 方法,並且總是回傳 500 Internal Server Error HTTP 回應(即使引發的例外是例如 Http404)。在 MIDDLEWARE 下,從中介層引發的例外會立即轉換為適當的 HTTP 回應,然後線路中的下一個中介層會看到該回應。中介層永遠不會因中介層引發例外而被跳過。

回到頂部