Django筆記二十九之中介軟體介紹

XHunter發表於2023-04-23

本文首發於公眾號:Hunter後端
原文連結:Django筆記二十九之中介軟體介紹

這一節介紹一下 Django 的中介軟體。

關於中介軟體,官方文件的解釋為:中介軟體是一個嵌入 Django 系統的 request 和 response 的鉤子框架,是一個能夠全域性改變 Django 輸入/輸出的系統。

我們可以這樣理解,一個 request 請求傳送到 Django 系統的過程中,在經過路由和檢視的處理前,會先經過一層處理,這個處理操作可以是日誌記錄,可以是登入驗證甚至你想在系統裡定義的功能,這個操作就是中介軟體實現的功能。

接下來我們將透過一個記錄請求的 ip 的功能的介紹來介紹一下中介軟體的實現流程。

以下是本篇筆記目錄:

  1. 請求經過 Django 然後返回的流程
  2. HttpRequest 和 HttpResponse 介紹
  3. 中介軟體的示例介紹
  4. 記錄訪問 ip 的功能實現

1、請求經過 Django 然後返回的流程

首先,前端發起一個請求,這個請求經由 web 伺服器轉發給 Django 系統,在進入 Django 系統後會先經過一系列的中介軟體的功能處理。

這個中介軟體會在 settings.py 裡定義,Django 系統預設自帶的中介軟體列表如下:

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

這些中介軟體我們也可以根據自己的需求自己定義,比如新加一個登入許可權,或者日誌記錄,或者對輸入的引數進行格式化處理也可以,或者自己想要設定的其他功能也行,具體怎麼設定在後面介紹。

在中介軟體處理的流程中,請求會被按照順序從上往下處理。

這個流程過後,一個 request 請求才會被進行 URL 的路徑匹配,如果匹配上,再去找相應的 views 檢視函式進行資料處理

views 處理完之後,會形成一個 response,返回,然後再次經歷這個中介軟體處理,因為在每一層中介軟體中都類似於一種巢狀,所以返回 response 的時候,是從下往上再次處理 response 的。

中介軟體處理結束之後再被返回出去,給到前端。

在這整個流程處理中,可以說中介軟體是進行了兩次操作,一個是進入的時候處理 request,一個是返回的時候處理 response。

2、HttpRequest 和 HttpResponse 介紹

我們先來看一個檢視函式:

def time_view(request):
    now = datetime.datetime.now()
    html = "<h1>now: %s</h1>abc\nabc" % now
    return HttpResponse(html)

當 Django 接收到一個請求,系統會建立一個 HttpRequest 物件,這個物件就是上面的檢視函式里的輸入引數,request

在對資料進行處理後,系統會返回一個 HttpResponse 物件,這個就是我們 return 的內容。

在一個 HttpRequest 物件裡,會包含請求的路徑、引數、請求方式、 cookie 等一切請求過來時的資料,我們可以在請求的時候根據需要存取。

在返回的 HttpResponse 中,可以是一個 html 頁面,也可以是 json 格式的資料,內容是可以自定義的,只要前端可以做相應的處理。

3、中介軟體的示例介紹

接下來我們定義一箇中介軟體,結構大致如下:

# huter/middleware.py

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        # 在請求進入檢視函式前的可以執行一些操作,針對 request
        print(request.path)
        response = self.get_response(request)
        
        # 在處理完請求後,可以執行一些操作,針對 response
        # log_response_info()
        return response

然後我們在 sttings.py 裡引入這個中介軟體,我們放到 MIDDLEWARE 列表的最下面,說明這個中介軟體會在其他中介軟體處理完 request 之後再處理:


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',
    'hunter.middleware.TestMiddleware',
]

在 SimpleMiddleware 這個類裡,call() 函式會自動呼叫,其中有一行,response = self.get_response(request)

在這一行函式之前,可以對請求的 request 做處理,包括我們前面說的各種功能,比如日誌、登入驗證、引數格式化等

在這一行函式之後,獲取了 response,這個就是檢視函式返回的 HttpResponse,我們可以在這裡對它的 response.status_code 狀態碼,和 response.content 做處理

比如前面 time_view 函式返回的內容是一個 JsonResponse:

return JsonResponse({"code": 0})

那麼在這裡我們可以獲取然後處理這個 HttpResponse:


def __call__(self, request):

    response = self.get_response(request)

    content = json.loads(response.content)
    content["msg"] = "success"
    response.content = json.dumps(content)
    return response

這裡只是一個示例,因為並不是所有的 HttpResponse 都是 json 格式的資料,所以可能需要加一個 try except 做下處理

還有一個功能是我之前做過的,就是在 headers 中加一個特定的字串,表示是我們系統專有的,用於前端判斷,這個很簡單,就是在 response 的 headers 引數中加一個鍵值對:

response.headers['system'] = 'hunter'

以上就是一個最簡單的中介軟體的處理方式。

process_view

除了 call 函式以外,還有一個 process_view() 的函式

這個函式是在 Django 系統呼叫 views 檢視函式前被呼叫,它的返回值是 None 或者一個 HttpResponse

如果為 None,那麼系統會接著呼叫檢視函式,如果是 HttpResponse 作為返回值,說明系統在這裡已經處理了請求,不需要再走views檢視函式,然後就會直接返回。

我們透過下面的例子來解釋這個函式作用。

4、記錄訪問 ip 的功能實現

假設我們需要禁止某一個或者某一個 ip 列表的請求訪問我們的系統

當然,這個操作,在 web 伺服器那部分就可以攔截,這裡就是單純舉個例子

那麼我們這樣設定一個 process_view 的功能,在真正執行檢視函式(也就是url 匹配上的 view函式)前,取出這個 request 的訪問的ip,然後進行判斷,如果在 禁止列表,那麼則直接返回一個禁止訪問的頁面。

class TestMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response


    def __call__(self, request):
        response = self.get_response(request)
        return response


    def process_view(self, request, view_func, *view_args, **view_kwargs):
        EXCLUDE_IPS = ['192.168.1.54']
        if 'HTTP_X_FORWARDED_FOR' in  request.META:
            ip =  request.META['HTTP_X_FORWARDED_FOR']
        else:
            ip = request.META['REMOTE_ADDR']
        if ip in EXCLUDE_IPS:
            return HttpResponse('<h1>您的ip被禁止</h1>')
        return None

在這裡,我們拿到請求的 ip 地址,去和我們定義的禁止ip列表做比較

如果在禁用列表,則直接返回 HttpResponse,不接著請求我們的服務來

否則,就返回 None,系統接收到 None 之後,會接著往下處理。

如果想獲取更多後端相關文章,可掃碼關注閱讀:

image

相關文章