零、參考
https://www.jb51.net/article/136422.htm
https://www.jb51.net/article/143832.htm
https://www.jb51.net/article/69953.htm
一、中間件的基本理解
我對django
中間件的理解:以組件化的形式,為大量的請求或響應提供批量化處理的接口,封裝着可插拔式的獨立附加功能邏輯,與基本web
業務邏輯功能解耦,通過hook
函數能更細致的處理請求或響應過程。
django
的中間件有如下特點:
1、每個中間件由一個類來表示
2、中間件的邏輯必須寫在特定的接口中,這些接口被稱為hook
函數
3、中間件的執行有順序依賴
4、hook
函數的執行有規定順序
5、中間件的啟用會影響所有的請求/響應
6、中間件是可插拔式的,這意味着可以不啟用任何中間件
7、中間件應該僅作為數據過濾器的角色對數據過濾、轉換、清洗,對數據的業務處理應該放在視圖系統中
8、如第7點,中間件應該作為額外功能模塊介入請求/響應流程,與普通業務處理模塊(視圖系統)解耦
二、中間件的系統定位
中間件在django
框架中的定位圖
三、中間件的配置
配置中間件類
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
'''
自定義類名,繼承內置的中間件混合類。
hook函數有固定的接口,自定義邏輯處理代碼
'''
def process_request(self, request):
pass
def process_view(self, request, callback, callback_args, callback_kwargs):
pass
def process_exception(self, request, exception):
pass
def process_template_response(self, request, response):
return response
def process_response(self, request, response):
return response
編寫中間件hook
函數邏輯
1、process_request(self, request)
參數request
是HttpRequest
對象,此hook
函數將會在路由分發前執行,有兩類返回值:
1. return None # 請求流程將會繼續按照原計划執行,這應該是默認設置
2. return HttpResponse # 請求將會跳轉到當前中間件的process_response函數處理並進入響應流程
注意:雖然return一個非None且非HttpResonse的值也會使得流程跳轉到響應流程,不過並不建議這么做,因為每一個process_response函數都期望接收到一個HttpResponse對象以便做進一步的處理,而不是收到一個奇怪的字符串或者數字。
注意:進入響應流程的入口是當前中間件的process_response
2、process_view(self, request, callback, callback_args, callback_kwargs)
請求流程完成路由分發后,在執行視圖函數前將會執行此hook
函數。此函數的callback
是對路由分發確定的視圖函數的引用,callback_args
, callback_kwargs
是傳遞給視圖函數的參數,有兩類返回值:
1. return None # 請求流程將會按照原計划繼續,這應該是默認設置
2.return HttpResponse # 請求將會跳轉到最后一個中間件的process_response函數處理並進入響應流程
注意:進入響應流程的入口是最后一個中間件的process_response
3、process_template_response
在 view
視圖函數中使用 render
渲染一個模版對象完成之后被調用,它必須返回一個render
方法執行后的response
對象。
4、process_exception(self, request, exception)
當視圖函數執行出錯的時候,會把錯誤拋給此hook
函數,有兩類返回值:
1. return None # 將會把錯誤對象exception提交給前一個中間件的process_exception處理
2. return HttpResponse # 將會跳轉到最后一個中間件的process_response函數處理並進入響應流程
注意:不應該return exception
注意:進入響應流程的入口是最后一個中間件的process_response
5、process_response(self, request, response)
此hook
函數將在響應流程中執行,函數必須返回HttpResponse對象
return HttpResponse # 把響應對象交給前一個中間件的process_response函數處理,如果已經是第一個中間件,將會交給wsgi服務器處理並發送給用戶瀏覽器。
注意:必須返回HttpResponse對象
啟用中間件
在項目settings文件中添加對中間件類的引用以啟動中間件
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',
'app01.my_middlewares.MyMiddleware', # 添加對自定義中間件類的引用以啟動
]
四、中間件的執行流程
中間件及hook
函數執行流程(省略process_template_response
)
五、中間件與裝飾器之間的思考
中間件的功能划分遵循原則:視圖函數僅完成本應完成的工作,額外的功能通過中間件來單獨提供。
中間件是可插拔式即意味着中間件的啟用和禁用均不會影響視圖函數的原始工作,這非常像之前學習過的python
裝飾器。python
裝飾器實現了設計模式中的裝飾模式,裝飾器的目的是:在保持原有函數功能的基礎之上,新增額外的功能,且新增的功能應該與原函數功能解耦,裝飾器也可以有選擇的增加或者移除。通過自己的研究和網上各大神的博客學習中發現,django
的中間件其實也是一種裝飾模式,而且可以和python
的裝飾器用法高度適配,我用如下兩張圖來對django
中間件和裝飾器進行了轉換。
圖一、django
中間件到裝飾器的轉換
圖二、django
中間件到裝飾器的轉換
python
多重裝飾器
雖然還沒研究過django
中間件的源代碼,不過我想先嘗試着使用python
的裝飾器來模擬中間件的效果。
首先,要理解裝飾器的核心知識:利用閉包特性來保存內層函數的執行上下文。正因為閉包的存在,內層函數的執行上下文(執行環境)即使在外層函數結束后依然可以被保存,這就意味着在外層函數結束后,依然可以正確的執行內層函數。 (ps:如果不使用閉包,外層函數結束后,該函數中的所有變量都會被銷毀)
其次,裝飾器可以迭代使用。
迭代是重復反饋過程的活動,其目的通常是為了逼近所需目標或結果。每一次對過程的重復稱為一次“迭代”,而每一次迭代得到的結果會作為下一次迭代的初始值。
---百度百科
就像這樣:
@IPFilter
@UserAuth
@DataTransform
@TrafficLog
def index(request):
# somecode...
return response
利用裝飾器函數模擬中間件效果
現在我們通過一個多重函數裝飾器簡單的模擬一下中間件的效果,需求如下:
有一個ip
黑名單列表,列表中的ip
不能訪問頁面。此外,有三個函數需要定義:
一個簡單的show_page
函數,將會模擬用戶訪問某一個頁面,並返回簡單的內容(當前用戶的ip
)。
一個filter_ip
裝飾器,過濾惡意ip
,如果用戶ip
在黑名單中就無法正常訪問頁面。
一個traffic_log
裝飾器,對正常訪問的流量進行統計。
基礎需求:通過自定義一個request
對象模擬用戶瀏覽器發出的http
請求對象,request
直接執行show_page
視圖函數以得到期望訪問的http
頁面。
額外需求:通過添加以上兩個裝飾器來增加ip
過濾和流量統計的功能。
代碼定義如下:
# 黑名單的定義
black_ip_list = ['10.1.1.1', '172.16.1.1', '192.168.1.1']
# 這里簡單的使用全局變量來表示統計流量
traffic_count = 0
# request對象的定義
class Request(object):
def __init__(self, source_ip):
self.source_ip = source_ip
# filter_ip過濾器函數的定義
def filter_ip(func):
def inner(request):
source_ip = request.source_ip
if source_ip in black_ip_list:
response = '你的ip在黑名單中'
else:
response = func(request)
return response
return inner
# traffic_log流量統計函數的定義
def traffic_log(func):
def inner(request):
global traffic_count
traffic_count += 1
print('當前頁面被有效請求的次數是:', traffic_count)
response = func(request)
return response
return inner
# show_page視圖函數的定義
def show_page(request):
source_ip = request.source_ip
response = '模擬的目標頁面內容,此用戶的ip是-->' + source_ip
return response
結果1,實現最基本的用戶訪問
結果2,實現ip
黑名單過濾
結果3,實現有效流量統計
結果4,實現ip
黑名單過濾+有效流量統計(特別注意順序依賴)
利用裝飾器類模擬中間件效果
雖然簡單的模擬出了中間件的可插拔、功能解耦、批量請求處理等功能,但還做的不夠好,我們可以基於上面的代碼,再做一些必要的封裝,代碼如下:
class TrafficLogMiddleware(object):
traffic_count = 0
def __init__(self, func):
self.func = func
def __call__(self, request):
self.traffic_count += 1
print('當前頁面被有效請求的次數是:', self.traffic_count)
response = self.func(request)
return response
class FilterIPMiddleware(object):
black_ip_list = ['10.1.1.1', '172.16.1.1', '192.168.1.1']
def __init__(self, func):
self.func = func
def __call__(self, request):
source_ip = request.source_ip
if source_ip in self.black_ip_list:
response = '你的ip在黑名單中'
else:
response = self.func(request)
return response
class Request(object):
def __init__(self, source_ip):
self.source_ip = source_ip
@FilterIPMiddleware
@TrafficLogMiddleware
def show_page(request):
source_ip = request.source_ip
response = '模擬的目標頁面內容,此用戶的ip是-->' + source_ip
return response
感覺不像django
的中間件接口?可以這樣寫:
class Middleware(object):
def __init__(self, func):
self.func = func
def __call__(self, request):
response = self.process_request(request)
if not response:
response = self.func(request)
response = self.process_response(request, response)
return response
def process_request(self, request):
pass
def process_response(self, request, response):
return response
class TrafficLogMiddleware(Middleware):
traffic_count = 0
def process_request(self, request):
self.traffic_count += 1
print('當前頁面被有效請求的次數是:', self.traffic_count)
def process_response(self, request, response):
return response
class FilterIPMiddleware(Middleware):
black_ip_list = ['10.1.1.1', '172.16.1.1', '192.168.1.1']
def process_request(self, request):
source_ip = request.source_ip
if source_ip in self.black_ip_list:
response = '你的ip在黑名單中'
else:
response = None
return response
def process_response(self, request, response):
return response
class Request(object):
def __init__(self, source_ip):
self.source_ip = source_ip
@FilterIPMiddleware
@TrafficLogMiddleware
def show_page(request):
source_ip = request.source_ip
response = '模擬的目標頁面內容,此用戶的ip是-->' + source_ip
return response
執行結果如下:
六、中間件的應用場景
中間件的啟用會影響所有的請求/響應--->適用於大量請求/響應的批量化處理場景
中間件相互之間功能解耦,順序依賴--->適合可插拔式的業務場景
中間件可以介入請求/響應流程--->適用於需要更加細致化處理請求/響應流程的業務場景
1、流量統計
2、惡意ip過濾
3、用戶區分
4、緩存CDN
5、URL過濾
6、數據預處理
......
七、內置中間件
django
框架內置了7個中間件,用於提供基本的http
請求和響應處理,內置中間件的基本學習可以參考:
https://www.jb51.net/article/69953.htm
八、總結
1、裝飾器和中間件都實現了裝飾模式,此模式的目的是為了在不修改原有模塊的條件下新增功能代碼,並可以提供可插拔的效果,同時新增代碼和原有代碼功能上解耦。
2、類比學習很重要,可以同時提升對兩個同類知識的理解。
3、中間件的角色應該是數據清洗/過濾/轉換器,不應該在中間件上處理業務邏輯,而只是處理數據約束,具體的業務邏輯應該放置在視圖函數中,這也是它的本職工作。
4、不要濫用中間件,過多的中間件會增加請求/響應流程的環節數,發生錯誤的時候提升排錯難度。中間件的使用應該依賴業務場景,在最合適的地方使用最合適的技術,才能發揮最高的效率。