中間件
中間件介紹
什么是中間件
官方的說法是:中間件是一個(gè)用來處理Django的請(qǐng)求和相應(yīng)的框架級(jí)別的鉤子。他是以個(gè)輕量滞造、低級(jí)別的插件系統(tǒng),用于在全局范圍內(nèi)改變Django和輸入和輸出栋烤。每個(gè)中間件組件都負(fù)責(zé)做一些特定的功能谒养。
但是由于其影響的是全局,所以要謹(jǐn)慎使用,使用不當(dāng)會(huì)影響性能买窟。
說的直白一些中間件就是幫助我們?cè)谝晥D函數(shù)執(zhí)行之前和執(zhí)行之后都可以做一些額外的操作丰泊,它本質(zhì)上就是一個(gè)自定義類,類中定義了幾個(gè)方法始绍,Django框架會(huì)在請(qǐng)求的特定時(shí)間去執(zhí)行這些方法瞳购。
我們一直都在使用中間件,只是沒有注意過罷了亏推。打開Django項(xiàng)目的settings.py文件学赛,可看到下面的MIDDLEWARE配置項(xiàng)。
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',
]
MIDDLEWARE配置項(xiàng)是一個(gè)列表吞杭,列表中是一個(gè)個(gè)字符串盏浇,這些字符串其實(shí)是一個(gè)個(gè)類,也就是一個(gè)個(gè)中間件芽狗。
我們之前已經(jīng)接觸過一個(gè)csrf相關(guān)的中間件了绢掰?我們一開始讓大家把他注釋掉,再提交post請(qǐng)求的時(shí)候童擎,就不會(huì)被forbidden了滴劲,后來學(xué)會(huì)使用csrf_token之后就不再注釋這個(gè)中間件了。
那接下來就學(xué)習(xí)中間件中的方法以及這些方法什么時(shí)候被執(zhí)行顾复。
自定義中間件
中間件可以定義五個(gè)方法哑芹,分別是:
- process_request(self, request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self, request, response)
- process_exception(self, request, exception)
- process_response(self, request, response)
以上方法d的返回值可以使None回一個(gè)HttpResponse對(duì)象, 如果是None捕透, 則繼續(xù)安好django定義的規(guī)則向后繼續(xù)執(zhí)行聪姿,如果是HttpResponse對(duì)象,則直接將該對(duì)象返回給用戶乙嘀。
自定義一個(gè)中間件
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")
def process_response(self, request, response):
print("MD1里面的 process_response")
return response
process_request
process_request有一個(gè)參數(shù)末购,就是request,這個(gè)request和視圖函數(shù)中的request是一樣的虎谢。
它的返回值可以是None也可以是HttpResponse對(duì)象盟榴。返回值是None的話,按正常流程繼續(xù)走婴噩,交給下一個(gè)中間件處理擎场,如果是HttpResponse對(duì)象,Django將不執(zhí)行視圖函數(shù)几莽,而將相應(yīng)對(duì)象返回給瀏覽器迅办。
我們來看看多個(gè)中間件時(shí),Django是如何執(zhí)行其中的process_request方法的章蚣。
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass
在settings.py的MIDDLEWARE配置項(xiàng)中注冊(cè)上述兩個(gè)自定義中間件:
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',
'middlewares.MD1', # 自定義中間件MD1
'middlewares.MD2' # 自定義中間件MD2
]
此時(shí)我們?cè)L問一個(gè)視圖站欺,會(huì)發(fā)現(xiàn)終端打印出以下內(nèi)容:
MD1里面的 process_request
MD2里面的 process_request
app01 中的 index視圖
把MD1和MD2的位置調(diào)換一下,再訪問一個(gè)視圖,會(huì)發(fā)現(xiàn)終端打印的內(nèi)容如下:
MD2里面的 process_request
MD1里面的 process_request
app01 中的 index視圖
看結(jié)果我們知道:是凸函數(shù)還是最后追星的矾策,MD2比MD1限制性自己的process_request方法磷账。
再打印一下兩個(gè)自定義中間件中process_request方法中的request參數(shù),會(huì)發(fā)現(xiàn)他們是同一個(gè)對(duì)象贾虽。
總結(jié):
1逃糟、中間件的process_request方法是在執(zhí)行是凸函數(shù)之前執(zhí)行的。
2蓬豁、當(dāng)配置多個(gè)中間件時(shí)绰咽,會(huì)按照MIDDLEWARE中的注冊(cè)順序,也就是列表的索引值庆尘,從前往后依次執(zhí)行的剃诅。
3、不同中間件之間傳遞的request都是同一個(gè)對(duì)象
多個(gè)中間件中的process_response方法是按照MIDDLEWARE中的注冊(cè)順序的倒序執(zhí)行的驶忌,也就是說第一個(gè)中間件的process_request方法先執(zhí)行而它的process_response方法最后執(zhí)行矛辕。
process_response
他有兩個(gè)參數(shù),一個(gè)是request付魔,一個(gè)是response聊品,request就是上述例子中一樣的對(duì)象,response是視圖函數(shù)返回的HttpResponse對(duì)象几苍。該方法的返回值也必須是HttpResponse對(duì)象翻屈。
給上述的M1和M2加上process_response方法:
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")
def process_response(self, request, response):
print("MD1里面的 process_response")
return response
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass
def process_response(self, request, response):
print("MD2里面的 process_response")
return response
訪問一下視圖,看一下終端的輸出:
MD2里面的 process_request
MD1里面的 process_request
app01 中的 index視圖
MD1里面的 process_response
MD2里面的 process_response
多個(gè)中間件中的process_response方法是按照MIDDLEWARE中的注冊(cè)順序倒序執(zhí)行的妻坝,也就是說第一個(gè)中間件的process_request方法首先執(zhí)行伸眶,而它的process_response方法最后執(zhí)行,最后一個(gè)中間件的process_request方法最后一個(gè)執(zhí)行刽宪,它的process_response方法是最先執(zhí)行厘贼。
process_view
process_view(self, request, view_func, view_args, view_kwargs)
該方法有四個(gè)參數(shù)
request是HttpRequest對(duì)象。
view_func是Django即將使用的視圖函數(shù)圣拄。 (它是實(shí)際的函數(shù)對(duì)象嘴秸,而不是函數(shù)的名稱作為字符串。)
view_args是將傳遞給視圖的位置參數(shù)的列表.
view_kwargs是將傳遞給視圖的關(guān)鍵字參數(shù)的字典庇谆。 view_args和view_kwargs都不包含第一個(gè)視圖參數(shù)(request)岳掐。
Django會(huì)在調(diào)用視圖函數(shù)之前調(diào)用process_view方法。
它應(yīng)該返回None或一個(gè)HttpResponse對(duì)象饭耳。 如果返回None串述,Django將繼續(xù)處理這個(gè)請(qǐng)求,執(zhí)行任何其他中間件的process_view方法哥攘,然后在執(zhí)行相應(yīng)的視圖剖煌。 如果它返回一個(gè)HttpResponse對(duì)象材鹦,Django不會(huì)調(diào)用適當(dāng)?shù)囊晥D函數(shù)逝淹。 它將執(zhí)行中間件的process_response方法并將應(yīng)用到該HttpResponse并返回結(jié)果耕姊。
給MD1和MD2添加process_view方法:
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")
def process_response(self, request, response):
print("MD1里面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__)
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass
def process_response(self, request, response):
print("MD2里面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__)
訪問index視圖函數(shù),看一下輸出結(jié)果:
MD2里面的 process_request
MD1里面的 process_request
MD2 中的process_view
<function index at 0x000001DE68317488> index
MD1 中的process_view
<function index at 0x000001DE68317488> index
app01 中的 index視圖
MD1里面的 process_response
MD2里面的 process_response
process_view方法是在process_request之后栅葡,視圖函數(shù)之前執(zhí)行的茉兰,執(zhí)行順序按照MIDDLEWARE中的注冊(cè)順序從前到后順序執(zhí)行的
process_exception
process_exception(self, request, exception)
該方法兩個(gè)參數(shù):
一個(gè)HttpRequest對(duì)象
一個(gè)exception是視圖函數(shù)異常產(chǎn)生的Exception對(duì)象。
這個(gè)方法只有在視圖函數(shù)中出現(xiàn)異常了才執(zhí)行欣簇,它返回的值可以是一個(gè)None也可以是一個(gè)HttpResponse對(duì)象规脸。如果是HttpResponse對(duì)象,Django將調(diào)用模板和中間件中的process_response方法熊咽,并返回給瀏覽器莫鸭,否則將默認(rèn)處理異常。如果返回一個(gè)None横殴,則交給下一個(gè)中間件的process_exception方法來處理異常被因。它的執(zhí)行順序也是按照中間件注冊(cè)順序的倒序執(zhí)行。
給MD1和MD2添加上這個(gè)方法:
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")
def process_response(self, request, response):
print("MD1里面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass
def process_response(self, request, response):
print("MD2里面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("MD2 中的process_exception")
如果視圖函數(shù)中無異常衫仑,process_exception方法不執(zhí)行梨与。
想辦法,在視圖函數(shù)中拋出一個(gè)異常:
def index(request):
print("app01 中的 index視圖")
raise ValueError("呵呵")
return HttpResponse("O98K")
在MD1的process_exception中返回一個(gè)響應(yīng)對(duì)象:
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")
def process_response(self, request, response):
print("MD1里面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
return HttpResponse(str(exception)) # 返回一個(gè)響應(yīng)對(duì)象
看輸出結(jié)果:
MD2里面的 process_request
MD1里面的 process_request
MD2 中的process_view
<function index at 0x0000022C09727488> index
MD1 中的process_view
<function index at 0x0000022C09727488> index
app01 中的 index視圖
呵呵
MD1 中的process_exception
MD1里面的 process_response
MD2里面的 process_response
注意文狱,這里并沒有執(zhí)行MD2的process_exception方法粥鞋,因?yàn)镸D1中的process_exception方法直接返回了一個(gè)響應(yīng)對(duì)象
process_template_response
process_template_response(self, request, response)
它的參數(shù),一個(gè)HttpRequest對(duì)象瞄崇,response是TemplateResponse對(duì)象(由視圖函數(shù)或者中間件產(chǎn)生)呻粹。
process_template_response是在視圖函數(shù)執(zhí)行完成后立即執(zhí)行,但是它有一個(gè)前提條件苏研,那就是視圖函數(shù)返回的對(duì)象有一個(gè)render()方法(或者表明該對(duì)象是一個(gè)TemplateResponse對(duì)象或等價(jià)方法)等浊。
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")
def process_response(self, request, response):
print("MD1里面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
return HttpResponse(str(exception))
def process_template_response(self, request, response):
print("MD1 中的process_template_response")
return response
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass
def process_response(self, request, response):
print("MD2里面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("MD2 中的process_exception")
def process_template_response(self, request, response):
print("MD2 中的process_template_response")
return response
views.py
def index(request):
print("app01 中的 index視圖")
def render():
print("in index/render")
return HttpResponse("O98K")
rep = HttpResponse("OK")
rep.render = render
return rep
訪問index視圖,終端輸出結(jié)果:
MD2里面的 process_request
MD1里面的 process_request
MD2 中的process_view
<function index at 0x000001C111B97488> index
MD1 中的process_view
<function index at 0x000001C111B97488> index
app01 中的 index視圖
MD1 中的process_template_response
MD2 中的process_template_response
in index/render
MD1里面的 process_response
MD2里面的 process_response
從結(jié)果看出:
視圖函數(shù)執(zhí)行完之后楣富,立即執(zhí)行了中間件的process_template_response方法凿掂,順序是倒序,先執(zhí)行MD1的纹蝴,在執(zhí)行MD2的庄萎,接著執(zhí)行了視圖函數(shù)返回的HttpResponse對(duì)象的render方法,返回了一個(gè)新的HttpResponse對(duì)象塘安,接著執(zhí)行中間件的process_response方法糠涛。
中間件的執(zhí)行流程
請(qǐng)求到達(dá)中間件之后,先按照正序執(zhí)行每個(gè)注冊(cè)中間件的process_reques方法兼犯,process_request方法返回的值是None忍捡,就依次執(zhí)行集漾,如果返回的值是HttpResponse對(duì)象,不再執(zhí)行后面的process_request方法砸脊,而是執(zhí)行當(dāng)前對(duì)應(yīng)中間件的process_response方法具篇,將HttpResponse對(duì)象返回給瀏覽器。也就是說:如果MIDDLEWARE中注冊(cè)了6個(gè)中間件凌埂,執(zhí)行過程中驱显,第3個(gè)中間件返回了一個(gè)HttpResponse對(duì)象,那么第4,5,6中間件的process_request和process_response方法都不執(zhí)行瞳抓,順序執(zhí)行3,2,1中間件的process_response方法埃疫。
process_request方法都執(zhí)行完后,匹配路由孩哑,找到要執(zhí)行的視圖函數(shù)栓霜,先不執(zhí)行視圖函數(shù),先執(zhí)行中間件中的process_view方法横蜒,process_view方法返回None胳蛮,繼續(xù)按順序執(zhí)行,所有process_view方法執(zhí)行完后執(zhí)行視圖函數(shù)愁铺。加入中間件3 的process_view方法返回了HttpResponse對(duì)象鹰霍,則4,5,6的process_view以及視圖函數(shù)都不執(zhí)行,直接從最后一個(gè)中間件茵乱,也就是中間件6的process_response方法開始倒序執(zhí)行茂洒。
process_template_response和process_exception兩個(gè)方法的觸發(fā)是有條件的,執(zhí)行順序也是倒序瓶竭《缴祝總結(jié)所有的執(zhí)行流程如下:
中間件版登錄驗(yàn)證
中間件版的登錄驗(yàn)證需要依靠session,所以數(shù)據(jù)庫(kù)中要有django_session表斤贰。
urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/$', views.index),
url(r'^login/$', views.login, name='login'),
]
views.py
from django.shortcuts import render, HttpResponse, redirect
def index(request):
return HttpResponse('this is index')
def home(request):
return HttpResponse('this is home')
def login(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
if user == "Q1mi" and pwd == "123456":
# 設(shè)置session
request.session["user"] = user
# 獲取跳到登陸頁(yè)面之前的URL
next_url = request.GET.get("next")
# 如果有智哀,就跳轉(zhuǎn)回登陸之前的URL
if next_url:
return redirect(next_url)
# 否則默認(rèn)跳轉(zhuǎn)到index頁(yè)面
else:
return redirect("/index/")
return render(request, "login.html")
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登錄頁(yè)面</title>
</head>
<body>
<form action="{% url 'login' %}">
<p>
<label for="user">用戶名:</label>
<input type="text" name="user" id="user">
</p>
<p>
<label for="pwd">密 碼:</label>
<input type="text" name="pwd" id="pwd">
</p>
<input type="submit" value="登錄">
</form>
</body>
</html>
middlewares.py
class AuthMD(MiddlewareMixin):
white_list = ['/login/', ] # 白名單
balck_list = ['/black/', ] # 黑名單
def process_request(self, request):
from django.shortcuts import redirect, HttpResponse
next_url = request.path_info
print(request.path_info, request.get_full_path())
if next_url in self.white_list or request.session.get("user"):
return
elif next_url in self.balck_list:
return HttpResponse('This is an illegal URL')
else:
return redirect("/login/?next={}".format(next_url))
在settings.py中注冊(cè)
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',
'middlewares.AuthMD',
]
AuthMD中間件注冊(cè)后,所有的請(qǐng)求都要走AuthMD的process_request方法荧恍。
訪問的URL在白名單內(nèi)或者session中有user用戶名瓷叫,則不做阻攔走正常流程;
如果URL在黑名單中送巡,則返回This is an illegal URL的字符串摹菠;
正常的URL但是需要登錄后訪問,讓瀏覽器跳轉(zhuǎn)到登錄頁(yè)面骗爆。
注:AuthMD中間件中需要session次氨,所以AuthMD注冊(cè)的位置要在session中間的下方。
附加:Django請(qǐng)求流程圖