我們在使用python的一些庫時嚼隘,會遇到中間件這個概念诽里,比如scrapy和Django,那么什么是中間件呢飞蛹?
什么是中間件
中間件就是在目標(biāo)和結(jié)果之間進(jìn)行的額外處理過程谤狡,在Django中就是request和response之間進(jìn)行的處理,相對來說實現(xiàn)起來比較簡單卧檐,但是要注意它是對全局有效的墓懂,可以在全局范圍內(nèi)改變輸入和輸出結(jié)果,因此需要謹(jǐn)慎使用霉囚,否則不僅會造成難以定位的錯誤捕仔,而且可能會影響整體性能。
中間件有什么用
如果想要修改HttpRequest或者HttpResponse盈罐,就可以通過中間件來實現(xiàn)榜跌。
- 登陸認(rèn)證:在中間件中加入登陸認(rèn)證,所有請求就自動擁有登陸認(rèn)證盅粪,如果需要放開部分路由钓葫,只需要特殊處理就可以了。
- 流量統(tǒng)計:可以針對一些渲染頁面統(tǒng)計訪問流量票顾。
- 惡意請求攔截:統(tǒng)計IP請求次數(shù)础浮,可以進(jìn)行頻次限制或者封禁IP。
中間件執(zhí)行流程
在Django中自定義中間件是非常簡單的奠骄,在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',
]
只要把添加的中間件配置在這里就可以了豆同。每一個中間件都是一個類,多個中間件可以寫在同一個文件含鳞,也可以在獨立文件中影锈。每個中間件可以包含五個方法:
process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)
我在網(wǎng)上找到這么一張圖片,說明了請求的數(shù)據(jù)流在Django中間件當(dāng)中的執(zhí)行流程
中間件函數(shù)執(zhí)行流程
- 請求到達(dá)中間件后先依次執(zhí)行每個中間件的process_request函數(shù)
- 然后再依次執(zhí)行每個中間件的process_view函數(shù),找到我們的視圖函數(shù)
- 執(zhí)行視圖函數(shù)處理請求數(shù)據(jù)
- 如果在上面的過程中出現(xiàn)異常精居,則依次反方向執(zhí)行每個中間件的process_exception函數(shù)
- 如果請求包含模板渲染锄禽,則依次反方向執(zhí)行每個中間件的process_template_response函數(shù)
- 最后依次反方向執(zhí)行每個中間件的process_response函數(shù)
以上這些執(zhí)行函數(shù)將返回None或者HttpResponse對象,如果返回None靴姿,則交給下一個中間件的對應(yīng)函數(shù)處理沃但;如果返回HttpResponse對象,則將其返回給用戶
在這些中間件的執(zhí)行函數(shù)中佛吓,我們最常用的就是process_request和process_response函數(shù)宵晚,通常用來在視圖函數(shù)處理前和視圖函數(shù)處理后執(zhí)行一些相應(yīng)的操作,這個要根據(jù)我們的業(yè)務(wù)需求维雇,選擇不同的處理過程淤刃。例如:進(jìn)行登陸認(rèn)證,因為必須要在視圖函數(shù)處理前進(jìn)行認(rèn)證吱型,我們可以在process_request中處理逸贾;攜帶認(rèn)證cookies信息,就可以在process_response函數(shù)中給response對象增加指定cookies值津滞。
中間件回調(diào)函數(shù)執(zhí)行
- Request函數(shù):process_request(self, request) 執(zhí)行時機:當(dāng)接收到前端請求铝侵,并生成request對象,但是仍未解析url触徐,未確定當(dāng)前要運行的視圖函數(shù)咪鲜。 如果返回None,Django將繼續(xù)處理下一個中間件的request函數(shù)撞鹉;如果返回HttpResponse對象疟丙,Django將不再執(zhí)行其他除process_response以外的所有函數(shù),包括后面的process_request函數(shù)鸟雏、其他中間件函數(shù)以及視圖函數(shù)享郊。
- View函數(shù):process_view(self, request, callback, callback_args, callback_kwargs) 執(zhí)行時機:在執(zhí)行完所有中間件的process_request函數(shù),并且已經(jīng)匹配到要執(zhí)行的視圖函數(shù)崔慧,但是還沒有調(diào)用視圖函數(shù)之前拂蝎。 callback:要執(zhí)行的視圖函數(shù)對象(就是我們所寫的視圖處理函數(shù)) callback_args:視圖函數(shù)的位置參數(shù)列表(不包含self和request) callback_kwargs:視圖函數(shù)的關(guān)鍵字參數(shù) 如果返回None,Django將繼續(xù)處理下一個中間件的request函數(shù)惶室;如果返回HttpResponse對象,Django將不再執(zhí)行其他除process_response以外的所有函數(shù)玄货,包括后面的process_request函數(shù)皇钞、其他中間件函數(shù)以及視圖函數(shù)。
- Template函數(shù):process_template_response(self, request, response) 執(zhí)行時機:只有在視圖函數(shù)的返回對象中有render方法才會執(zhí)行松捉,并把render方法的返回值返回給用戶夹界。
- Exception函數(shù):process_exception(self, request, exception) 執(zhí)行時機:如果在執(zhí)行過程中出現(xiàn)問題,并且拋出一個未被捕獲的異常時才被調(diào)用隘世。我們可以用它來捕獲請求錯誤可柿,發(fā)送通知或者恢復(fù)錯誤場景鸠踪。 如果返回None,Django將使用框架內(nèi)置異常處理复斥,并繼續(xù)交給下一個exception函數(shù)营密;如果返回HttpResponse對象,Django將不再執(zhí)行其他除process_response以外的所有函數(shù)目锭,并中斷異常處理评汰。
- Response函數(shù):process_response(self, request, response) 執(zhí)行時機:執(zhí)行完view函數(shù)并生成response之后,幾乎是必執(zhí)行的函數(shù)痢虹。 返回并且只能被去、必須返回HttpResponse對象,否則會導(dǎo)致HTTP請求中斷奖唯。
自定義中間件
- 創(chuàng)建中間件類
from django.utils.deprecation import MiddlewareMixin
class MyCustomMiddleware1(MiddlewareMixin):
def process_request(self, request):
print('MyCustomMiddleware1')
def process_response(self, request, response):
print('返回 MyCustomMiddleware1')
return response
class MyCustomMiddleware2(MiddlewareMixin):
def process_request(self, request):
print('MyCustomMiddleware2')
def process_response(self, request, response):
print('返回 MyCustomMiddleware2')
return response
- 注冊中間件
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',
'MyMiddleware.MyCustomMiddleware1',
'MyMiddleware.MyCustomMiddleware2'
]
輸出結(jié)果:
MyCustomMiddleware1
MyCustomMiddleware2
返回 MyCustomMiddleware2
返回 MyCustomMiddleware1
系統(tǒng)中間件的用途
- django.middleware.security.SecurityMiddleware 主要是針對安全訪問處理惨缆,就是把http請求重定向到https請求
- django.contrib.sessions.middleware.SessionMiddleware 在Django中我們用的request.session就是在process_request中進(jìn)行處理的,根據(jù)我們在settings中配置的SESSION_COOKIE_NAME變量丰捷,從cookies中獲取對應(yīng)的值坯墨,從表中查詢出session值,創(chuàng)建session對象瓢阴,賦值給request_session對象畅蹂。
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
在process_response函數(shù)中,給response對象設(shè)置SESSION_COOKIE_NAME值和過期時間等荣恐。
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
samesite=settings.SESSION_COOKIE_SAMESITE
)
- django.middleware.common.CommonMiddleware 檢測是否允許瀏覽器類型
if 'HTTP_USER_AGENT' in request.META:
for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
raise PermissionDenied('Forbidden user agent')
檢查是否需要添加/液斜,主要是根據(jù)settings中APPEND_SLASH配置
if self.should_redirect_with_slash(request):
path = self.get_full_path_with_slash(request)
else:
path = request.get_full_path()
在process_response函數(shù)中,會判斷是否需要把404的請求重新定向到我們需要的頁面
- django.middleware.csrf.CsrfViewMiddleware 這個很明顯就是我們Django框架的csrf驗證了叠穆,主要是process_view中的處理少漆,從函數(shù)處理我們可以看到以下幾點:
- request請求中包含csrf_processing_done屬性,則不進(jìn)行csrf驗證
- 視圖函數(shù)中包含csrf_exempt屬性硼被,則不進(jìn)行csrf驗證
- 如果是GET示损、HEAD、OPTIONS嚷硫、TRACE請求检访,則不進(jìn)行csrf驗證
- request請求中包含_dont_enforce_csrf_checks屬性,則不進(jìn)行csrf驗證
- https請求頭中如果不包含HTTP_REFERER仔掸,則拒絕訪問
- 請求頭中不包含CSRF_COOKIE脆贵,則拒絕訪問
- POST請求中攜帶csrfmiddlewaretoken參數(shù),如果驗證通過就可以訪問
- PUT/DELETE請求頭中攜帶CSRF_HEADER_NAME配置起暮,如果驗證通過就可以訪問
- django.contrib.auth.middleware.AuthenticationMiddleware 這個中間件中為我們的request對象添加了user屬性卖氨,主要是獲取session中SESSION_KEY值(settings配置中),從用戶表中查詢對應(yīng)主鍵,得到用戶對象筒捺,將其付給request.user
- django.contrib.messages.middleware.MessageMiddleware Django的消息框架柏腻,主要是向目標(biāo)中推送消息內(nèi)容,在前端可通過以下方式使用