django框架--中間件系統

零、參考

https://www.jb51.net/article/136422.htm
https://www.jb51.net/article/143832.htm
https://www.jb51.net/article/69953.htmweb

1、中間件的基本理解

我對django中間件的理解:以組件化的形式,爲大量的請求或響應提供批量化處理的接口,封裝着可插拔式的獨立附加功能邏輯,與基本web業務邏輯功能解耦,經過hook函數能更細緻的處理請求或響應過程。django

django的中間件有以下特色:
一、每一箇中間件由一個類來表示
二、中間件的邏輯必須寫在特定的接口中,這些接口被稱爲hook函數
三、中間件的執行有順序依賴
四、hook函數的執行有規定順序
五、中間件的啓用會影響全部的請求/響應
六、中間件是可插拔式的,這意味着能夠不啓用任何中間件
七、中間件應該僅做爲數據過濾器的角色對數據過濾、轉換、清洗,對數據的業務處理應該放在視圖系統中
八、如第7點,中間件應該做爲額外功能模塊介入請求/響應流程,與普通業務處理模塊(視圖系統)解耦設計模式

2、中間件的系統定位

中間件在django框架中的定位圖
瀏覽器

3、中間件的配置

配置中間件類緩存

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函數邏輯服務器

一、process_request(self, request)
參數requestHttpRequest對象,此hook函數將會在路由分發前執行,有兩類返回值:session

1. return None  # 請求流程將會繼續按照原計劃執行,這應該是默認設置
2. return HttpResponse  # 請求將會跳轉到當前中間件的process_response函數處理並進入響應流程

注意:雖然return一個非None且非HttpResonse的值也會使得流程跳轉到響應流程,不過並不建議這麼作,由於每個process_response函數都指望接收到一個HttpResponse對象以便作進一步的處理,而不是收到一個奇怪的字符串或者數字。

注意:進入響應流程的入口是當前中間件的process_response

二、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

三、process_template_response
view 視圖函數中使用 render 渲染一個模版對象完成以後被調用,它必須返回一個render方法執行後的response對象。併發

四、process_exception(self, request, exception)
當視圖函數執行出錯的時候,會把錯誤拋給此hook函數,有兩類返回值:

1. return None  # 將會把錯誤對象exception提交給前一箇中間件的process_exception處理
2. return HttpResponse  # 將會跳轉到最後一箇中間件的process_response函數處理並進入響應流程

注意:不該該return exception

注意:進入響應流程的入口是最後一箇中間件的process_response

五、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',  # 添加對自定義中間件類的引用以啓動
]

4、中間件的執行流程

中間件及hook函數執行流程(省略process_template_response)

5、中間件與裝飾器之間的思考

中間件的功能劃分遵循原則:視圖函數僅完成本應完成的工做,額外的功能經過中間件來單獨提供
中間件是可插拔式即意味着中間件的啓用和禁用均不會影響視圖函數的原始工做,這很是像以前學習過的python裝飾器。python裝飾器實現了設計模式中的裝飾模式,裝飾器的目的是:在保持原有函數功能的基礎之上,新增額外的功能,且新增的功能應該與原函數功能解耦,裝飾器也能夠有選擇的增長或者移除。經過本身的研究和網上各大神的博客學習中發現,django的中間件其實也是一種裝飾模式,並且能夠和python的裝飾器用法高度適配,我用以下兩張圖來對django中間件和裝飾器進行了轉換。

圖1、django中間件到裝飾器的轉換

圖2、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

執行結果以下:

6、中間件的應用場景

中間件的啓用會影響全部的請求/響應--->適用於大量請求/響應的批量化處理場景
中間件相互之間功能解耦,順序依賴--->適合可插拔式的業務場景
中間件能夠介入請求/響應流程--->適用於須要更加細緻化處理請求/響應流程的業務場景
一、流量統計
二、惡意ip過濾
三、用戶區分
四、緩存CDN
五、URL過濾
六、數據預處理
......

7、內置中間件

django框架內置了7箇中間件,用於提供基本的http請求和響應處理,內置中間件的基本學習能夠參考:
https://www.jb51.net/article/69953.htm

8、總結

一、裝飾器和中間件都實現了裝飾模式,此模式的目的是爲了在不修改原有模塊的條件下新增功能代碼,並能夠提供可插拔的效果,同時新增代碼和原有代碼功能上解耦。 二、類比學習很重要,能夠同時提高對兩個同類知識的理解。 三、中間件的角色應該是數據清洗/過濾/轉換器,不該該在中間件上處理業務邏輯,而只是處理數據約束,具體的業務邏輯應該放置在視圖函數中,這也是它的本職工做。 四、不要濫用中間件,過多的中間件會增長請求/響應流程的環節數,發生錯誤的時候提高排錯難度。中間件的使用應該依賴業務場景,在最合適的地方使用最合適的技術,才能發揮最高的效率。

相關文章
相關標籤/搜索