中間件簡介
什么是中間件
中間件是一個用來處理Django的請求和響應的框架級別的鈎子。它是一個輕量、低級別的插件系統,用於在全局范圍內改變Django的輸入和輸出。每個中間件組件都負責做一些特定的功能。
說的直白一點中間件是幫助我們在視圖函數執行之前和執行之后都可以做一些額外的操作,它本質上就是一個自定義類,類中定義了幾個方法,Django框架會在請求的特定的時間去執行這些方法。
Django內置的中間件
其實我們一直都在使用中間件,只是沒有注意到而已,打開Django項目的Settings.py文件,看到MIDDLEWARE配置項。如下:
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配置項是一個列表,列表中是一個個字符串,這些字符串其實是一個個類,也就是一個個中間件。
自定義中間件
規則
1、要繼承MiddlewareMixin類(from django.utils.deprecation import MiddlewareMixin)
2、要重寫父類的方法
3、將類添加到Settings.py文件的MIDDLEWARE配置項里
父類的五個方法,如下:(主要的是process_request和process_response)
- 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)
以上方法的返回值可以是None或一個HttpResponse對象,如果是None,則繼續按照django定義的規則向后繼續執行,如果是HttpResponse對象,則直接將該對象返回給用戶。
小示例
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有一個參數,就是request,這個request和視圖函數中的request是一樣的。
它的返回值可以是None也可以是HttpResponse對象。返回值是None的話,按正常流程繼續走,交給下一個中間件處理,如果是HttpResponse對象,Django將不執行視圖函數,而將相應對象返回給瀏覽器。
我們來看看多個中間件時,Django是如何執行其中的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配置項中注冊上述兩個自定義中間件:
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
]
此時,我們訪問一個視圖(在瀏覽器中輸入視圖對應的路徑),會發現終端中打印如下內容:
MD1里面的 process_request MD2里面的 process_request app01 中的 index視圖
如果把MD1和MD2的位置調換一下,再訪問一個視圖,會發現終端中打印的內容如下:
MD2里面的 process_request MD1里面的 process_request app01 中的 index視圖
看結果我們知道:視圖函數還是最后執行的,MD2比MD1先執行自己的process_request方法。
在打印一下兩個自定義中間件中process_request方法中的request參數,會發現它們是同一個對象。
由此總結一下:
- 中間件的process_request方法是在執行視圖函數之前執行的。
- 當配置多個中間件時,會按照MIDDLEWARE中的注冊順序,也就是列表的索引值,從前到后依次執行的。
- 不同中間件之間傳遞的request都是同一個對象
process_response
它有兩個參數,一個是request,一個是response,request就是上述例子中一樣的對象,response是視圖函數返回的HttpResponse對象。該方法的返回值也必須是HttpResponse對象。
給上述的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
看結果可知:
process_response方法是在視圖函數之后執行的,並且順序是MD1比MD2先執行。(此時settings.py中 MD2比MD1先注冊)
多個中間件中的process_response方法是按照MIDDLEWARE中的注冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最后執行,最后一個中間件的process_request方法最后一個執行,它的process_response方法是最先執行。
process_view
process_view(self, request, view_func, view_args, view_kwargs)
該方法有四個參數
request是HttpRequest對象。
view_func是Django即將使用的視圖函數。 (它是實際的函數對象,而不是函數的名稱作為字符串。)
view_args是將傳遞給視圖的位置參數的列表.
view_kwargs是將傳遞給視圖的關鍵字參數的字典。 view_args和view_kwargs都不包含第一個視圖參數(request)。
Django會在調用視圖函數之前調用process_view方法。
它應該返回None或一個HttpResponse對象。 如果返回None,Django將繼續處理這個請求,執行任何其他中間件的process_view方法,然后在執行相應的視圖。 如果它返回一個HttpResponse對象,Django不會調用適當的視圖函數。 它將執行中間件的process_response方法並將應用到該HttpResponse並返回結果。
給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視圖函數,看一下輸出結果:
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之后,視圖函數之前執行的,執行順序按照MIDDLEWARE中的注冊順序從前到后順序執行的
process_exception
process_exception(self, request, exception)
該方法兩個參數:
一個HttpRequest對象
一個exception是視圖函數異常產生的Exception對象。
這個方法只有在視圖函數中出現異常了才執行,它返回的值可以是一個None也可以是一個HttpResponse對象。如果是HttpResponse對象,Django將調用模板和中間件中的process_response方法,並返回給瀏覽器,否則將默認處理異常。如果返回一個None,則交給下一個中間件的process_exception方法來處理異常。它的執行順序也是按照中間件注冊順序的倒序執行。
process_template_response(不常使用)
process_template_response(self, request, response)
它的參數,一個HttpRequest對象,response是TemplateResponse對象(由視圖函數或者中間件產生)。
process_template_response是在視圖函數執行完成后立即執行,但是它有一個前提條件,那就是視圖函數返回的對象有一個render()方法(或者表明該對象是一個TemplateResponse對象或等價方法)。
中間件的執行流程
上一部分,我們了解了中間件中的5個方法,它們的參數、返回值以及什么時候執行,現在總結一下中間件的執行流程。
請求到達中間件之后,先按照正序執行每個注冊中間件的process_reques方法,process_request方法返回的值是None,就依次執行,如果返回的值是HttpResponse對象,不再執行后面的process_request方法,而是執行當前對應中間件的process_response方法,將HttpResponse對象返回給瀏覽器。也就是說:如果MIDDLEWARE中注冊了6個中間件,執行過程中,第3個中間件返回了一個HttpResponse對象,那么第4,5,6中間件的process_request和process_response方法都不執行,順序執行3,2,1中間件的process_response方法。

process_request方法都執行完后,匹配路由,找到要執行的視圖函數,先不執行視圖函數,先執行中間件中的process_view方法,process_view方法返回None,繼續按順序執行,所有process_view方法執行完后執行視圖函數。加入中間件3 的process_view方法返回了HttpResponse對象,則4,5,6的process_view以及視圖函數都不執行,直接從最后一個中間件,也就是中間件6的process_response方法開始倒序執行。

process_template_response和process_exception兩個方法的觸發是有條件的,執行順序也是倒序。總結所有的執行流程如下:


中間件的應用(登錄時黑名單驗證)
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 == "fuyong" and pwd == "123456":
# 設置session
request.session["user"] = user
# 獲取跳到登陸頁面之前的URL
next_url = request.GET.get("next")
# 如果有,就跳轉回登陸之前的URL
if next_url:
return redirect(next_url)
# 否則默認跳轉到index頁面
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>登錄頁面</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>
login.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
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',
]
settings.py中注冊中間件
AuthMD中間件注冊后,所有的請求都要走AuthMD的process_request方法。
訪問的URL在白名單內或者session中有user用戶名,則不做阻攔走正常流程;
如果URL在黑名單中,則返回This is an illegal URL的字符串;
正常的URL但是需要登錄后訪問,讓瀏覽器跳轉到登錄頁面。
注:AuthMD中間件中需要session,所以AuthMD注冊的位置要在session中間的下方。
