Django的中介軟體原理&執行
分析Django的生命週期,我們知道所有的http請求都要經過Django的中介軟體.
假如現在有一個需求,所有到達服務端的url請求都在系統中記錄一條日誌,該怎麼做呢?
(寫的非常詳細呀!)
Django的中介軟體的簡介
Django的中介軟體類似於linux中的管道符
Django的中介軟體實質就是一個類,類之中有Django已經定義好了一些方法.
每個http請求都會執行中介軟體中的一個或多個方法:
執行流程圖:
1 進入Django中的請求都會執行process_request方法;
2 Django返回的資訊都會執行process_response方法.;
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', ]
匯入from django.middleware.csrf import CsrfViewMiddleware
模組,檢視其原始碼
class CsrfViewMiddleware(MiddlewareMixin)
可以看到CsrfViewMiddleware
是繼承MiddlewareMixin
這個中介軟體的
再檢視MiddlewareMixin
的原始碼
class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response
可以知道MiddlewareMixin
是呼叫了其內部的__call__
方法,__call__
方法以反射的方式執行process_request
和process_response
方法.
中介軟體的應用
新建一個middleware專案,在專案的根目錄中新建一個名為middleware
的包,在這個package中,新建一個middleware.py
檔案.
from django.utils.deprecation import MiddlewareMixin
class middle_ware1(MiddlewareMixin):
def process_request(self,request):
print("middle_ware1.process_request")
def process_response(self,request,response):
print("middle_ware1.process_response")
return response
在settings.py
配置檔案的第一行註冊這個中介軟體
MIDDLEWARE = [
'middleware.middleware.middle_ware1'
'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',
]
現在如果有http請求到達服務端,先執行中介軟體middle_ware1
的process_request
方法,再執行後面的中介軟體,然後到達檢視函式.
定義一個檢視函式index,配置好路由對映表
def index(request):
print("index page")
return HttpResponse("index")
啟動程式,在瀏覽器中輸入http://127.0.0.1:8000/index
,會在服務端的後臺打
middle_ware1.process_request
index page
middle_ware1.process_response
前端的頁面為顯示為:
index
通過這個我們知道,所有的請求都會經過middle_ware1
這個中介軟體,先執行process_request
方法,再執行檢視函式本身
最後執行process_response
方法,Django會把process_response
方法的返回值返回給客戶端.
現在再加一個定義一個middle_ware2
的中介軟體
from django.utils.deprecation import MiddlewareMixin
class middle_ware2(MiddlewareMixin):
def process_request(self,request):
print("middle_ware2.process_request")
def process_response(self,request,response):
print("middle_ware2.process_response")
return response
在settings.py配置檔案中注測:
MIDDLEWARE = [
'middleware.middleware.middle_ware1',
'middleware.middleware.middle_ware2',
'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',
]
重啟程式,再次重新整理網頁,服務端列印資訊
middle_ware1.process_request
middle_ware2.process_request
index page
middle_ware2.process_response
middle_ware1.process_response
中介軟體的依次執行為:
-
先執行middle_ware1的process_request方法,
-
再執行middle_ware2的類的process_request方法,
-
最後才執行Django的檢視函式.
-
返回時,先執行middle_ware2的類中的process_response函式,
-
然後再執行middle_ware1的類中的process_response函式.
上面的例子中,process_response
方法設定了return值.假如process_response
沒有return值,會怎麼樣呢??
把middle_ware1
中介軟體的process_response
方法中的return註釋掉,再次重新整理網頁,在瀏覽器的網頁上顯示報錯資訊,如下:
A server error occurred. Please contact the administrator.
http請求到達Django後,先經過自定義的中介軟體middle_ware1
和middle_ware2
,再經過Django內部定義的中介軟體到達檢視函式
檢視函式執行完成後,一定會返回一個HttpResponse
或者render
.
通過檢視render
的原始碼可知,render
方法本質上也是呼叫了HttpRespons
def render(request, template_name, context=None, content_type=None, status=None, using=None):
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
戶定義的中介軟體,最後返回給前端網頁.
所以使用者定義的中介軟體的process_response
方法必須設定返回值,否則程式會報錯.
同時,process_response
方法中的形參response
就是檢視函式的返回值.
那麼我們是否可以自己定義這個形參的返回值呢??
使用者發過來的請求資訊是固定的,因為所有的請求資訊和返回資訊都要經過中介軟體,中介軟體有可能會修改返回給使用者的資訊
,所以有可能會出現使用者收到的返回值與檢視函式的返回值不一樣的情況.
例如,返回給使用者的資訊包含響應頭和響應體,但是開發者在檢視函式中沒有設定響應頭,所以Django會在返回的資訊中自動加上響應頭.
前面,process_response
方法設定了返回值,process_request
中沒有設定返回值,如果為process_response
設定一個返回值,結果會怎麼樣呢??
為中介軟體middle_ware1
的process_request
方法設定返回值
return HttpResponse("request message")
重新整理瀏覽器,服務端列印資訊:
middle_ware1.process_request
middle_ware1.process_response
客戶端瀏覽器顯示資訊為:
request message
先執行process_request
方法,因為process_request
方法有返回值,程式不會再執行後面的中介軟體,而是直接執行process_response
方法,然後返回資訊給使用者.
在實際應用中,process_request
方法不能直接設定返回值.如果必須在設定返回值,必須在返回值前加入條件判斷語句.
常用在設定網站的黑名單的時候可以為process_request
方法設定返回值.
基於中介軟體實現的簡單使用者登入驗證
比如,在一個部落格系統中,所有的後臺管理頁面都必須有使用者登入後才能開啟,可以基於中介軟體來實現使用者登入驗證
定義中介軟體
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
class middle_ware1(MiddlewareMixin):
def process_request(self,request):
if request.path_info == "/login/":
return None
if not request.session.get("user_info"):
return redirect("/login/")
def process_response(self,request,response):
print("middle_ware1.process_response")
return response
在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',
'middleware.middleware.middle_ware1',
]
使用者請求index網頁,程式執行到process_request
,會執行if not語句,然後重定向到login
頁面,使使用者輸入使用者名稱和密碼登入進入後臺管理頁面.
Django中介軟體常用方法
除了上面已經用過的process_request
方法和porcess_response
方法,Django的中介軟體還有以下幾種方法.
process_view方法
定義兩個中介軟體如下
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
class middle_ware1(MiddlewareMixin):
def process_request(self,request):
print("middle_ware1.process_request")
def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware1.process_view")
def process_response(self,request,response):
print("middle_ware1.process_response")
return response
class middle_ware2(MiddlewareMixin):
def process_request(self,request):
print("middle_ware2.process_request")
def process_view(self,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware2.process_view")
def process_response(self,request,response):
print("middle_ware2.process_response")
return response
在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',
'middleware.middleware.middle_ware1',
'middleware.middleware.middle_ware2',
]
檢視函式
def index(request):
print("index page")
return HttpResponse("index")
在瀏覽器中輸入http://127.0.0.1:8000/index
頁面,在服務端列印資訊
middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middle_ware2.process_response
middle_ware1.process_response
在前端頁面顯示資訊:
index
程式執行流程:
先執行所有中介軟體中的process_request
方法-->進行路由匹配-->執行中介軟體中的的process_view
方法
-->執行對應的檢視函式-->執行中介軟體中的process_response
方法
上面的例子裡,process_view
方法裡沒有設定return值.為process_view
方法都設定return值,程式又會怎麼執行呢??
為process_view
方法設定返回值
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
class middle_ware1(MiddlewareMixin):
def process_request(self,request):
print("middle_ware1.process_request")
def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
return HttpResponse("middle_ware1.process_view")
def process_response(self,request,response):
print("middle_ware1.process_response")
return response
class middle_ware2(MiddlewareMixin):
def process_request(self,request):
print("middle_ware2.process_request")
def process_view(self,request,view_func,view_func_args,view_func_kwargs):
return HttpResponse("middle_ware2.process_view")
def process_response(self,request,response):
print("middle_ware2.process_response")
重新整理瀏覽器,在服務端列印資訊如下:
middle_ware1.process_request
middle_ware2.process_request
middle_ware2.process_response
middle_ware1.process_response
在客戶端的瀏覽器頁面顯示資訊如下:
iddle_ware1.process_view
可以看到檢視函式index
並沒有執行,程式執行兩個中介軟體的process_request
方法後,其次執行process_view
方法.
由於process_view
方法設定了return值,所以程式中的檢視函式並沒有執行,而是執行了中介軟體中的process_response
方法.
process_exception方法
修改中介軟體
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
class middle_ware1(MiddlewareMixin):
def process_request(self,request):
print("middle_ware1.process_request")
def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware1.process_view")
def process_exception(self,request,exvception):
print("middleware1.process_exception")
def process_response(self,request,response):
print("middle_ware1.process_response")
return response
class middle_ware2(MiddlewareMixin):
def process_request(self,request):
print("middle_ware2.process_request")
def process_view(self,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware2.process_view")
def process_exception(self,request,exception):
print("middleware2.process_exception")
def process_response(self,request,response):
print("middle_ware2.process_response")
return response
重新整理瀏覽器,服務端列印資訊
middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
hello,index page
middle_ware2.process_response
middle_ware1.process_response
根據服務端的列印資訊可以看到,process_exception
方法沒有執行,為什麼呢??
這是因為上面的程式碼沒有bug.當代碼執行錯誤,出現報錯資訊的時候,process_exception
才會執行
那現在就模擬讓程式出現錯誤,觀察process_exception
方法的執行情況
修改檢視函式
def index(request):
print("index page")
int("aaaa")
return HttpResponse("index")
重新整理瀏覽器,服務端後臺列印資訊
middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middleware2.process_exception
middleware1.process_exception
Internal Server Error: /index
Traceback (most recent call last):
......
ValueError: invalid literal for int() with base 10: 'aaaa'
middle_ware2.process_response
middle_ware1.process_response
由服務端的報錯資訊可知,這次process_exception
方法果然執行了.
由此我們知道,程式執行錯誤,中介軟體中的process_exception
方法才會執行,而程式正常執行的時候,這個方法則不會執行
剛才的程式碼裡,process_exception
方法沒有設定返回值,如果為process_exception
方法設定返回值,程式的執行流程會是怎麼的呢???
修改中介軟體,為兩個中介軟體的process_exception
方法都設定返回值
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
class middle_ware1(MiddlewareMixin):
def process_request(self,request):
print("middle_ware1.process_request")
def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware1.process_view")
def process_exception(self,request,exvception):
print("middleware1.process_exception")
return HttpResponse("bug1")
def process_response(self,request,response):
print("middle_ware1.process_response")
return response
class middle_ware2(MiddlewareMixin):
def process_request(self,request):
print("middle_ware2.process_request")
def process_view(self,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware2.process_view")
def process_exception(self,request,exception):
print("middleware2.process_exception")
return HttpResponse("bug2")
def process_response(self,request,response):
print("middle_ware2.process_response")
return response
檢視函式
def index(request):
print("index page")
int("aaaa")
return HttpResponse("index")
重新整理頁面,服務端列印資訊
middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middleware2.process_exception
middle_ware2.process_response
middle_ware1.process_response
而在瀏覽器的頁面則顯資訊
bug2
程式的執行流程為:
客戶端發出的http請求到達中介軟體,執行中介軟體中的process_request
方法,然後再執行路由匹配,然後執行process_view
方法,
然後執行相應的檢視函式,最後執行process_response
方法,返回資訊給客戶端瀏覽器.
如果執行檢視函式時出現執行錯誤,中介軟體中的process_exception
方法捕捉到異常就會執行,後續的process_exception
方法就不會再執行了.
process_exception
方法執行完畢,最後再執行所有的process_response
方法.
process_template_response方法
process_template_response方法預設也不會被執行,
修改中介軟體
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
class middle_ware1(MiddlewareMixin):
def process_request(self,request):
print("middle_ware1.process_request")
def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware1.process_view")
def process_exception(self,request,exvception):
print("middle_ware1.process_exception")
return HttpResponse("bug1")
def process_response(self,request,response):
print("middle_ware1.process_response")
return response
def process_template_response(self,request,response):
print("middle_ware1.process_template_response")
return response
class middle_ware2(MiddlewareMixin):
def process_request(self,request):
print("middle_ware2.process_request")
def process_view(self,request,view_func,view_func_args,view_func_kwargs):
print("middle_ware2.process_view")
def process_exception(self,request,exception):
print("middleware2.process_exception")
return HttpResponse("bug2")
def process_response(self,request,response):
print("middle_ware2.process_response")
return response
def process_template_response(self,request,response):
print("middle_ware2.process_template_response")
return response
修改檢視函式,使檢視函式正常執行
def index(request):
print("index page")
return HttpResponse("index")
重新整理瀏覽器,服務端後臺列印資訊如下:
middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middle_ware2.process_response
middle_ware1.process_response
可以看到,程式執行正常,process_template_response
方法沒有執行.
事實上,process_template_response
方法的執行取決於檢視函式的返回的資訊,
檢視函式如果使用render方法返回資訊,中介軟體裡的process_template_response
方法就會被執行.
修改檢視函式,定義一個類,返回其引數response
class MyResponse(object):
def __init__(self,response):
self.response=response
def render(self):
return self.response
def index(request):
response=HttpResponse("index page")
return MyResponse(response)
MyResponse類返回的是自定義的物件,這個物件裡邊呼叫了render
方法.
index檢視函式裡,先呼叫了HttpResponse
方法返回資訊,再使用MyResponse
類中的render
方法來返回HttpResponse
.
middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
middle_ware2.process_template_response
middle_ware1.process_template_response
middle_ware2.process_response
middle_ware1.process_response
可以看到,process_template_response
方法已經執行.
process_template_response
的用處
-
如果要對返回的HttpResponse資料進行處理,可以把要處理的資訊封裝在一個類裡
-
只要類裡定義了render方法,process_template_response方法才會執行.
總結(中介軟體的入口):5個
- 中介軟體裡本質上就是一個類,
- 對全域性的http請求做處理的時候可以使用中介軟體
- 中介軟體中的方法不一定要全部使用,需要哪個用哪個
- process_request方法不能有return,一定要使用return的時候,要配合條件判斷語句執行
- process_response方法一定要有return,否則程式會執行錯誤
- process_view方法不能有return,否則檢視函式不會執行
- process_exception方法只有在程式出現執行錯誤的時候才會執行
- process_exception方法設定return時,程式不會再執行後續中介軟體中的process_exception
- process_template_response方法只有在檢視函式中使用render方法返回資訊的時候才會執行