目錄
一、中間件簡(jiǎn)介
Django官方文檔描述
二、激活中間件
三、中間件包含的鉤子方法和應(yīng)用的順序
1. 鉤子方法的調(diào)用
1.1 process_request
1.2 process_view
1.3 process_template_response
1.4 process_response
1.5 process_exception
四、編寫自己的中間件
1. 正常流程
2. process_request返回HttpResponse
3. 加入process_view()方法
3.1 將上述中間件代碼中M3.process_request()返回一個(gè)HttpResponse(其余代碼不變):
3.2 M2.process_view()返回HttpResponse,M3.process_request()返回None,其余代碼不變:
4. 加入process_exception()方法
4.1 將上述中間件代碼中的M2.process_response()返回HttpResponse
五、中間件應(yīng)用場(chǎng)景
1、做IP限制
2、URL訪問過濾
3、緩存(還記得CDN嗎?)
一、中間件簡(jiǎn)介
在django中,中間件其實(shí)就是一個(gè)類,在請(qǐng)求到來和結(jié)束后,django會(huì)根據(jù)自己的規(guī)則在合適的時(shí)機(jī)執(zhí)行中間件中相應(yīng)的方法。
Django官方文檔描述
中間件是一個(gè)鉤子框架,它們可以介入Django 的請(qǐng)求和響應(yīng)處理過程。它是一個(gè)輕量級(jí)、底層的“插件”系統(tǒng),用于在全局修改Django 的輸入或輸出。
每個(gè)中間件組件負(fù)責(zé)完成某個(gè)特定的功能。例如,Django 包含的一個(gè)中間件組件AuthenticationMiddleware ,它使用會(huì)話將用戶和請(qǐng)求關(guān)聯(lián)起來。
這篇文檔講解中間件如何工作、如何激活中間件以及如何編寫你自己的中間件。Django集成了一些內(nèi)置的中間件可以直接開箱即用。它們被歸檔在 .內(nèi)置中間件參考
二、激活中間件
要激活一個(gè)中間件組件,需要把它添加到Django項(xiàng)目的配置文件settings.py中的MIDDLEWARE列表中。
在MIDDLEWARE中,每一個(gè)中間件組件用字符串的方式描述:一個(gè)完整的Python全路徑加上中間件的類名稱。例如,使用django-admin startproject
創(chuàng)建項(xiàng)目的時(shí)候生成的默認(rèn)值:
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.locale.LocaleMiddleware', # admin界面默認(rèn)是顯示英語,如果要顯示中文,就添加這一行中間件
]
Django的程序中,中間件不是必需的---只要你喜歡,MIDDLEWARE列表可以為空,但是強(qiáng)烈推薦你至少使用CommonMiddleware這個(gè)中間件。
MIDDLEWARE中的順序非常重要,因?yàn)橐粋€(gè)中間件可能依賴于另外一個(gè)。因?yàn)橐粋€(gè)中間件可能依賴于另外一個(gè)。例如,AuthenticationMiddleware在會(huì)話中儲(chǔ)存已認(rèn)證的用戶。所以它必須在SessionMiddleware之后運(yùn)行。一些關(guān)于Django中間件類的順序的常見提示,請(qǐng)見中間件排序。
三、中間件包含的鉤子方法和應(yīng)用的順序
在請(qǐng)求階段中,調(diào)用視圖之前,Django會(huì)按照MIDDLEWARE中定義的順序自頂向下應(yīng)用中間件。會(huì)用到兩個(gè)鉤子:
process_request(request)
process_view(request, view_func, view_args, view_kwargs)
在響應(yīng)階段中,調(diào)用視圖之后,中間件會(huì)按照相反的順序應(yīng)用,自底向上。會(huì)用到三個(gè)鉤子:
process_exception(request, exception
process_template_response(request, response)
process_response(request, response)
上面5種鉤子方法,你可以在自己寫的中間件類中定義其中一種或多種。
1. 鉤子方法的調(diào)用
1.1 process_request
process_request(request)
request是一個(gè)HttpRequest 對(duì)象。
Django在執(zhí)行視圖函數(shù)前,先從上往下調(diào)用每個(gè)中間件的process_request()方法。它應(yīng)該返回一個(gè)None或一個(gè)HttpResponse對(duì)象。如果返回None(不寫返回值相當(dāng)于返回None),Django會(huì)繼續(xù)調(diào)用下一個(gè)中間件的process_request()方法,當(dāng)所有中間件的process_request()方法全部調(diào)用完畢且沒有返回HttpResponse對(duì)象時(shí),再?gòu)牡谝粋€(gè)中間件開始依次調(diào)用每個(gè)中間件的process_view()方法,最后執(zhí)行對(duì)應(yīng)的視圖函數(shù)。如果它返回一個(gè)HttpResponse對(duì)象,Django就不用再去調(diào)用其它中間件的process_request()、process_view()或process_exception()和視圖函數(shù)了,它將對(duì)HttpResponse 運(yùn)用響應(yīng)階段的中間件,并返回結(jié)果。
1.2 process_view
process_view(request, view_func, view_args, view_kwargs)
request是一個(gè)HttpRequest對(duì)象。view_func是個(gè)視圖函數(shù), view_args是一個(gè)會(huì)被傳遞到視圖的位置參數(shù)列表,而view_kwargs 是一個(gè)會(huì)被傳遞到視圖的關(guān)鍵字參數(shù)字典。 view_args和 view_kwargs 都不包括第一個(gè)視圖參數(shù)(request)。
process_view()會(huì)在Django 調(diào)用視圖之前被調(diào)用。
它將返回None 或一個(gè)HttpResponse 對(duì)象。如果返回None,Django 將會(huì)繼續(xù)執(zhí)行其它的process_view() 中間件,然后調(diào)用對(duì)應(yīng)的視圖。如果返回一個(gè)HttpResponse對(duì)象,Django 就不用再去調(diào)用其它的view 或exception 中間件,或?qū)?yīng)的視圖;它將對(duì)HttpResponse 運(yùn)用響應(yīng)階段的中間件,并返回結(jié)果。
注意:在中間件內(nèi)部,從process_request 或process_view 中訪問request.POST 或request.REQUEST 將阻礙該中間件之后的所有視圖無法修改請(qǐng)求的上傳處理程序,一般情況下要避免這樣使用。
類CsrfViewMiddleware可以被認(rèn)為是個(gè)例外,因?yàn)樗峁ヽsrf_exempt() 和csrf_protect()兩個(gè)裝飾器,允許視圖顯式控制在哪個(gè)點(diǎn)需要開啟CSRF驗(yàn)證。
1.3 process_template_response
這個(gè)鉤子實(shí)際中使用的較少
process_template_response(request, response)
request是一個(gè)HttpRequest對(duì)象。response是一個(gè)TemplateResponse對(duì)象(或等價(jià)的對(duì)象),由Django視圖或者中間件返回。
如果響應(yīng)的實(shí)例有render()方法,process_template_response()在視圖剛好執(zhí)行完畢之后被調(diào)用,這表明了它是一個(gè)TemplateResponse對(duì)象(或等價(jià)的對(duì)象)。
這個(gè)方法必須返回一個(gè)實(shí)現(xiàn)了render方法的響應(yīng)對(duì)象。它可以修改給定的response對(duì)象,通過修改 response.template_name和response.context_data或者它可以創(chuàng)建一個(gè)全新的 TemplateResponse或等價(jià)的對(duì)象。
你不需要顯式渲染響應(yīng) —— 一旦所有的模板響應(yīng)中間件被調(diào)用,響應(yīng)會(huì)自動(dòng)被渲染。
在一個(gè)響應(yīng)的處理期間,中間件以相反的順序運(yùn)行,這包括process_template_response()。
1.4 process_response
process_response(request, response)
process_response()在所有響應(yīng)返回瀏覽器之前被調(diào)用。
request是一個(gè)HttpRequest對(duì)象。response是Django視圖或者中間件返回的HttpResponse或者StreamingHttpResponse對(duì)象。
這個(gè)方法必須返回HttpResponse或者StreamingHttpResponse對(duì)象。它可以改變已有的response,或者創(chuàng)建并返回新的HttpResponse或StreamingHttpResponse對(duì)象。
不像 process_request()和process_view()方法,即使同一個(gè)中間件類中的process_request()和process_view()方法會(huì)因?yàn)榍懊娴囊粋€(gè)中間件返回HttpResponse而被跳過,process_response()方法總是會(huì)被調(diào)用。特別是,這意味著你的process_response()方法不能依賴于process_request()方法中的設(shè)置。
注意:最后,記住在響應(yīng)階段中,中間件以相反的順序被應(yīng)用,自底向上。意思是定義在MIDDLEWARE最底下的類的process_response()會(huì)最先被運(yùn)行。
1.5 process_exception
process_exception(request, exception)
當(dāng)一個(gè)視圖拋出異常時(shí),Django會(huì)調(diào)用process_exception()來處理。
request是一個(gè)HttpRequest對(duì)象。exception是一個(gè)被視圖中的方法拋出來的 Exception對(duì)象。
process_exception()應(yīng)該返回一個(gè)None 或者一個(gè)HttpResponse對(duì)象。如果它返回一個(gè)HttpResponse對(duì)象,模型響應(yīng)和響應(yīng)中間件會(huì)被應(yīng)用,響應(yīng)結(jié)果會(huì)返回給瀏覽器。否則, 默認(rèn)的異常處理機(jī)制將會(huì)被觸發(fā)。
再次提醒,在處理響應(yīng)期間,中間件的執(zhí)行順序是倒序執(zhí)行的,這包括process_exception。如果一個(gè)異常處理的中間件返回了一個(gè)響應(yīng),那這個(gè)中間件上面的中間件都將不會(huì)被調(diào)用。
四、編寫自己的中間件
Django版本
>> django-admin version
1.11.7
自己寫的中間件(類)必須繼承MiddlewareMixin
1. 正常流程
只有process_request和process_response時(shí)
from django.shortcuts import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self,request):
print('中間件: M1.process_request')
def process_response(self,request,response):
print('中間件:M1.process_response')
return response
class M2(MiddlewareMixin):
def process_request(self,request):
print('中間件:M2.process_request')
def process_response(self,request,response):
print('中間件:M2.process_response')
return response
class M3(MiddlewareMixin):
def process_request(self,request):
print('中間件:M3.process_request')
def process_response(self,request,response):
print('中間件:M3.process_response')
return response
在settings.py中注冊(cè)自己編寫的中間件:
MIDDLEWARE = [
...
'middleware.testMiddleWare.M1',
'middleware.testMiddleWare.M2',
'middleware.testMiddleWare.M3',
...
]
寫一個(gè)簡(jiǎn)單的視圖test
def test(request):
print('views.test')
return HttpResponse('ok')
urls.py中添加一條url
urlpatterns = [
...
url(r'test/', views.test),
]
訪問http://127.0.0.1:8000/test/后,后端打印結(jié)果如下:
中間件: M1.process_request
中間件:M2.process_request
中間件:M3.process_request
視圖函數(shù):views.test
中間件:M3.process_response
中間件:M2.process_response
中間件:M1.process_response
從輸出結(jié)果,可以得出執(zhí)行流程如下圖:
2. process_request返回HttpResponse
將上述中間件代碼中的M2.process_request()返回HttpResponse(其余代碼不變):
class M2(MiddlewareMixin):
def process_request(self,request):
print('中間件:M2.process_request')
return HttpResponse('前端顯示:中間件:M2.process_request')
def process_response(self,request,response):
print('中間件:M2.process_response')
return response
前端顯示結(jié)果:
前端顯示:中間件:M2.process_request
后端輸入結(jié)果:
中間件: M1.process_request
中間件:M2.process_request
中間件:M2.process_response
中間件:M1.process_response
從輸出結(jié)果,可以得出執(zhí)行流程如下圖:
M2.process_request()返回HttpResponse后,不再執(zhí)行后面中間件的process_request()方法和視圖函數(shù),直接依次往上(倒序)執(zhí)行M2和M1的process_response()方法。
3. 加入process_view()方法
from django.shortcuts import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self,request):
print('中間件: M1.process_request')
def process_response(self,request,response):
print('中間件:M1.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M1.process_view')
class M2(MiddlewareMixin):
def process_request(self,request):
print('中間件:M2.process_request')
def process_response(self,request,response):
print('中間件:M2.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M2.process_view')
print('callback:',callback)
class M3(MiddlewareMixin):
def process_request(self,request):
print('中間件:M3.process_request')
def process_response(self,request,response):
print('中間件:M3.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M3.process_view')
前端顯示視圖函數(shù)test返回的:ok
后端輸出結(jié)果如下:
中間件: M1.process_request
中間件:M2.process_request
中間件:M3.process_request
中間件:M1.process_view
中間件:M2.process_view
callback: <function test at 0x00000000045A8A60>
中間件:M3.process_view
視圖函數(shù):views.test
中間件:M3.process_response
中間件:M2.process_response
中間件:M1.process_response
依結(jié)果得出執(zhí)行流程如下:
3.1 將上述中間件代碼中M3.process_request()返回一個(gè)HttpResponse(其余代碼不變):
class M3(MiddlewareMixin):
def process_request(self,request):
print('中間件:M3.process_request')
return HttpResponse('前端顯示:中間件:M3.process_request')
def process_response(self,request,response):
print('中間件:M3.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M3.process_view')
前端頁面顯示:
前端顯示:中間件:M3.process_request
后端輸出結(jié)果如下:
中間件: M1.process_request
中間件:M2.process_request
中間件:M3.process_request
中間件:M3.process_response
中間件:M2.process_response
中間件:M1.process_response
從輸入結(jié)果,可以得到如下執(zhí)行流程:
從上述結(jié)果中可以看出,當(dāng)process_request()返回HttpResponse時(shí),就不再執(zhí)行process_view()了。process_view()的callback參數(shù)是被執(zhí)行的視圖函數(shù)對(duì)象。
3.2 M2.process_view()返回HttpResponse,M3.process_request()返回None,其余代碼不變:
注意:函數(shù)沒有返回值時(shí),默認(rèn)返回None。
class M2(MiddlewareMixin):
def process_request(self,request):
print('中間件:M2.process_request')
def process_response(self,request,response):
print('中間件:M2.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M2.process_view')
return HttpResponse('前端顯示:中間件:M2.process_view')
前端頁面顯示:
前端顯示:中間件:M2.process_view
后端輸出結(jié)果如下:
中間件: M1.process_request
中間件:M2.process_request
中間件:M3.process_request
中間件:M1.process_view
中間件:M2.process_view
中間件:M3.process_response
中間件:M2.process_response
中間件:M1.process_response
從上述結(jié)果可以得出如下執(zhí)行流程:
從上圖中可以看出,當(dāng)中間某一個(gè)中間件的process_view()返回HttpResponse時(shí),就不再執(zhí)行下面中間件的process_view()和視圖函數(shù)了。
4. 加入process_exception()方法
此方法是當(dāng)視圖函數(shù)中拋出異常時(shí)執(zhí)行,當(dāng)返回None時(shí),前端頁面會(huì)拋出一個(gè)異常頁面,當(dāng)返回HttpResponse時(shí),前端頁面會(huì)顯示你定制的內(nèi)容。
from django.shortcuts import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self,request):
print('中間件: M1.process_request')
def process_response(self,request,response):
print('中間件:M1.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M1.process_view')
# return HttpResponse('M1.process_view')
def process_exception(self,request,exception):
print('中間件:M1.process_exception')
class M2(MiddlewareMixin):
def process_request(self,request):
print('中間件:M2.process_request')
# return HttpResponse('前端顯示:中間件:M2.process_request')
def process_response(self,request,response):
print('中間件:M2.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M2.process_view')
# return HttpResponse('前端顯示:中間件:M2.process_view')
def process_exception(self,request,exception):
print('中間件:M2.process_exception')
class M3(MiddlewareMixin):
def process_request(self,request):
print('中間件:M3.process_request')
# return HttpResponse('前端顯示:中間件:M3.process_request')
def process_response(self,request,response):
print('中間件:M3.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M3.process_view')
# return HttpResponse('前端顯示:中間件:M3.process_view')
def process_exception(self,request,exception):
print('中間件:M3.process_exception')
故意給視圖函數(shù)test加一個(gè)錯(cuò)誤:
def test(request):
int('abc') #將字母轉(zhuǎn)換成整型一定會(huì)拋出異常
print('視圖函數(shù):views.test')
return HttpResponse('ok')
前端頁面顯示如下圖:
4.1 將上述中間件代碼中的M2.process_response()返回HttpResponse
class M2(MiddlewareMixin):
def process_request(self,request):
print('中間件:M2.process_request')
def process_response(self,request,response):
print('中間件:M2.process_response')
return response
def process_view(self,request,callback,args,kwargs):
print('中間件:M2.process_view')
def process_exception(self,request,exception):
print('中間件:M2.process_exception')
return HttpResponse("500錯(cuò)誤")
前面頁面顯示:
500錯(cuò)誤
后端輸出結(jié)果:
中間件: M1.process_request
中間件:M2.process_request
中間件:M3.process_request
中間件:M1.process_view
中間件:M2.process_view
中間件:M3.process_view
中間件:M3.process_exception
中間件:M2.process_exception
中間件:M3.process_response
中間件:M2.process_response
中間件:M1.process_response
執(zhí)行流程如下圖:
從上圖可以看出,當(dāng)中一個(gè)中間件的process_exception()方法捕獲到視圖函數(shù)中拋出異常的后并返回HttpResponse,則前端頁面就顯示你自定制的內(nèi)容。后端程序也不會(huì)出現(xiàn)異常。并且前面的中間件process_exception()也不再執(zhí)行。
五、中間件應(yīng)用場(chǎng)景
由于中間件工作在 視圖函數(shù)執(zhí)行前、執(zhí)行后(像不像所有視圖函數(shù)的裝飾器!)適合所有的請(qǐng)求/一部分請(qǐng)求做批量處理
1、做IP限制
放在 中間件類的列表中,阻止某些IP訪問了;
2、URL訪問過濾
如果用戶訪問的是login視圖(放過)
如果訪問其他視圖(需要檢測(cè)是不是有session已經(jīng)有了放行,沒有返回login),這樣就省得在 多個(gè)視圖函數(shù)上寫裝飾器了!
3、緩存(還記得CDN嗎?)
客戶端請(qǐng)求來了,中間件去緩存看看有沒有數(shù)據(jù),有直接返回給用戶,沒有再去邏輯層 執(zhí)行視圖函數(shù)