1. 程式人生 > >django框架--中介軟體系統

django框架--中介軟體系統

目錄

零、參考

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) 引數requestHttpRequest物件,此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_responseview 檢視函式中使用 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、不要濫用中介軟體,過多的中介軟體會增加請求/響應流程的環節數,發生錯誤的時候提升排錯難度。中介軟體的使用應該依賴業務場景,在最合適的地方使用最合適的技術,才能發揮最高的效率。