Python 100 Days Day47 Middleware Application
21 Sep 2022 | beginner pythonauthor: jackfrued
中間件的應用
之前我們已經實現了用戶必須登錄才能投票的限制,但是一個新的問題來了。如果我們的應用中有很多功能都需要用戶先登錄才能執行,例如將前面導出Excel報表和查看統計圖表的功能都做了必須登錄才能訪問的限制,那麽我們是不是需要在每個視圖函數中添加代碼來檢查session中是否包含userid的代碼呢?答案是否定的,如果這樣做了,我們的視圖函數中必然會充斥著大量的重覆代碼。編程大師Martin Fowler曾經說過:代碼有很多種壞味道,重覆是最壞的一種。在Python程序中,我們可以通過裝飾器來為函數提供額外的能力;在Django項目中,我們可以把類似於驗證用戶是否登錄這樣的重覆性代碼放到中間件中。
Django中間件概述
中間件是安插在Web應用請求和響應過程之間的組件,它在整個Web應用中扮演了攔截過濾器的角色,通過中間件可以攔截請求和響應,並對請求和響應進行過濾(簡單的說就是執行額外的處理)。通常,一個中間件組件只專注於完成一件特定的事,例如:Django框架通過SessionMiddleware中間件實現了對session的支持,又通過AuthenticationMiddleware中間件實現了基於session的請求認證。通過把多個中間件組合在一起,我們可以完成更為覆雜的任務,Django框架就是這麽做的。
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',
]
我們稍微為大家解釋一下這些中間件的作用:
CommonMiddleware- 基礎設置中間件,可以處理以下一些配置參數。- DISALLOWED_USER_AGENTS - 不被允許的用戶代理(瀏覽器)
- APPEND_SLASH - 是否追加
/ - USE_ETAG - 瀏覽器緩存相關
SecurityMiddleware- 安全相關中間件,可以處理和安全相關的配置項。- SECURE_HSTS_SECONDS - 強制使用HTTPS的時間
- SECURE_HSTS_INCLUDE_SUBDOMAINS - HTTPS是否覆蓋子域名
- SECURE_CONTENT_TYPE_NOSNIFF - 是否允許瀏覽器推斷內容類型
- SECURE_BROWSER_XSS_FILTER - 是否啟用跨站腳本攻擊過濾器
- SECURE_SSL_REDIRECT - 是否重定向到HTTPS連接
- SECURE_REDIRECT_EXEMPT - 免除重定向到HTTPS
SessionMiddleware- 會話中間件。CsrfViewMiddleware- 通過生成令牌,防範跨請求份偽的造中間件。XFrameOptionsMiddleware- 通過設置請求頭參數,防範點擊劫持攻擊的中間件。
在請求的過程中,上面的中間件會按照書寫的順序從上到下執行,然後是URL解析,最後請求才會來到視圖函數;在響應的過程中,上面的中間件會按照書寫的順序從下到上執行,與請求時中間件執行的順序正好相反。
自定義中間件
Django中的中間件有兩種實現方式:基於類的實現方式和基於函數的實現方式,後者更接近於裝飾器的寫法。裝飾器實際上是代理模式的應用,將橫切關注功能(與正常業務邏輯沒有必然聯系的功能,例如:身份認證、日志記錄、編碼轉換之類的功能)置於代理中,由代理對象來完成被代理對象的行為並添加額外的功能。中間件對用戶請求和響應進行攔截過濾並增加額外的處理,在這一點上它跟裝飾器是完全一致的,所以基於函數的寫法來實現中間件就跟裝飾器的寫法幾乎一模一樣。下面我們用自定義的中間件來實現用戶登錄驗證的功能。
"""
middlewares.py
"""
from django.http import JsonResponse
from django.shortcuts import redirect
# 需要登錄才能訪問的資源路徑
LOGIN_REQUIRED_URLS = {'/praise/', '/criticize/', '/excel/', '/teachers_data/'}
def check_login_middleware(get_resp):
def wrapper(request, *args, **kwargs):
# 請求的資源路徑在上面的集合中
if request.path in LOGIN_REQUIRED_URLS:
# 會話中包含userid則視為已經登錄
if 'userid' not in request.session:
# 判斷是不是Ajax請求
if request.is_ajax():
# Ajax請求返回JSON數據提示用戶登錄
return JsonResponse({'code': 10003, 'hint': '請先登錄'})
else:
backurl = request.get_full_path()
# 非Ajax請求直接重定向到登錄頁
return redirect(f'/login/?backurl={backurl}')
return get_resp(request, *args, **kwargs)
return wrapper
當然,我們也可以定義一個類來充當裝飾器,如果類中有__call__魔術方法,這個類的對象就像函數一樣可調用,所以下面是另一種實現中間件的方式,道理跟上面的代碼完全一樣。
還有一種基於類實現中間件的方式,這種方式在較新版本的Django中已經不推薦使用了,但是大家接觸到的代碼中,仍然有可能遇到這種寫法,大致的代碼如下所示。
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
pass
def process_view(self, request, view_func, view_args, view_kwargs):
pass
def process_template_response(self, request, response):
pass
def process_response(self, request, response):
pass
def process_exception(self, request, exception):
pass
上面類中的五個方法都是中間件的鉤子函數,分別在收到用戶請求、進入視圖函數之前、渲染模板、返回響應和出現異常的時候被回調。當然,寫不寫這些方法是根據中間件的需求來確定的,並不是所有的場景都需要重寫五個方法,下面的圖相信能夠幫助大家理解這種寫法。

寫好中間件代碼後,需要修改配置文件來激活中間件使其生效。
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',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'vote.middlewares.check_login_middleware',
]
注意上面這個中間件列表中元素的順序,當收到來自用戶的請求時,中間件按照從上到下的順序依次執行,這行完這些中間件以後,請求才會最終到達視圖函數。當然,在這個過程中,用戶的請求可以被攔截,就像上面我們自定義的中間件那樣,如果用戶在沒有登錄的情況下訪問了受保護的資源,中間件會將請求直接重定向到登錄頁,後面的中間件和視圖函數將不再執行。在響應用戶請求的過程中,上面的中間件會按照從下到上的順序依次執行,這樣的話我們還可以對響應做進一步的處理。
中間件執行的順序是非常重要的,對於有依賴關系的中間件必須保證被依賴的中間件要置於依賴它的中間件的前面,就好比我們剛才自定義的中間件要放到SessionMiddleware的後面,因為我們要依賴這個中間件為請求綁定的session對象才能判定用戶是否登錄。
Comments