忠誠的皇家守衛團——中間件

1、什麼是中間件呢?
2、中間件的五員大將
3、中間件的執行流程
 
 
 
1、什麼是中間件呢?

枯燥的中間件概念
按照官方的說法,中間件是一個用來 處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局變量中改變Django的輸入和輸入。每一箇中間件組件都負責 作一些特定的功能。但因爲其影響的是 全局,所以要謹慎使用。
那到底什麼是中間件呢?中間件其實就是幫助咱們在 Django的運行過程當中的某個環節來執行特定的操做,好比在視圖函數執行以後response一個特定的內容。它的本質是定義了一個類, 類中定義了一些方法,Django框架會在請求的特定的時間去執行這些方法。
 
emmm...看不懂,讓咱們來從源碼中找到答案吧!
1.讓咱們先來看一下一直在默默付出的Django自帶的中間件(setting.py中的MIDDLEWARE配置項中)
# 中間件相關的都配置在這裏
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',    # session相關的中間件
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',    # csrf相關的中間件
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
 
2.既然Django自帶有中間件,那麼咱們不妨去觀摩一下它們的本質(源碼)吧!
# 查看中間件源碼的方法:
# 1.先寫from A import B
# 2.按住Ctrl,鼠標同時點擊B
from django.middleware.security import SecurityMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.middleware.common import CommonMiddleware
from django.middleware.csrf import CsrfViewMiddleware
 
3.如下是它們的大體內容:
# 因爲源碼太多,咱們只截取大體框架,具體咱們以後再討論
django.middleware.security.SecurityMiddleware 的源碼
 
class SecurityMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):    # 初始化
        pass
    def process_request(self, request):    # 處理請求的實例方法
        pass
    def process_response(self, request, response):    #處理響應的實例方法
        pass
django.contrib.sessions.middleware.SessionMiddleware 的源碼
 
class SessionMiddleware(MiddlewareMixin):    # 定義了一個類,繼承MiddlewareMixin
    def __init__(self, get_response=None):    # 初始化
        pass
    def process_request(self, request):    # 處理請求的實例方法
        pass
    def process_response(self, request, response):    # 處理響應的實例方法
        pass
django.middleware.csrf.CsrfViewMiddleware 的源碼
 
class CsrfViewMiddleware(MiddlewareMixin):    # 定義了一個類,繼承MiddlewareMixin
    def _accept(self, request):    # 私有方法
        pass
    def _reject(self, request, reason):    # 私有方法
        pass
    def _get_token(self, request):    # 私有方法
        pass
    def _set_token(self, request, response):    # 私有方法
        pass
    def process_request(self, request):    # 處理請求的實例方法
        pass
    def process_view(self, request, callback, callback_args, callback_kwargs):    # 處理視圖的實例方法
        pass
    def process_response(self, request, response):    # 處理響應的實例方法
        pass
 
4.經過對Django自帶的幾個中間件源碼的大體分析,咱們能夠發現, 其實中間件就是定義了一個類,類中寫了幾個方法:好比處理響應的、處理視圖的、處理請求的..這些東西好像很固定?!好吧,接下來讓咱們來具體分析一下它的實例方法!
 
 
 
2、中間件的五員大將

中間件的五員大將,分別是:(主要的是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對象,則 直接將該對象返回給用戶。
第一個要了解的是兩名守門大將(request 和 response)
 
第一步:新建一個與manage.py同級別的py文件
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin    # 導入要繼承的MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse    # 導入三件套
 
class test1(MiddlewareMixin):    #定義一個名爲test1的類,繼承MiddlewareMixin
    def process_request(self, request):    #處理請求的實例方法
        print('-----test1-----request-----')    #打印用來分析test1的request、response與test2的request、response的執行順序
        return    #返回None,正常執行,若是返回的是HttpResponse,則不執行後面的內容,直接往回響應
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response    #經過分析源碼獲得(這裏須要本身分析)返回response是正常執行,所以,只須要中間件打印,打印以後就正常執行
 
class test2(MiddlewareMixin):    #定義一個名爲test2的類,與test1作對比
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
第二步:把新寫的中間件添加到Django的配置中
MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'mymiddleware.test1',    # mymiddleware.py文件下的test1類
    'mymiddleware.test2',    mymiddleware.py文件下的test2類
]
 
第三步:運行server,查看結果並分析
-----test1-----request-----
-----test2-----request-----
-----views-----    #這裏是我在views中寫了一個函數,這個函數打印-----views-----
-----test2-----response-----
-----test1-----response-----
 
第四步:分析
          a. 首先運行test1類中處理request的實例方法,打印出了 -----test1-----request-----
          b. 而後運行test2類中處理request的實例方法,打印出了 -----test2-----request-----
          c. 接着運行到視圖函數中,打印出了 -----views-----
          d. 而後從視圖函數中拿到response,開始往回走,因爲來的時候先通過test1,在通過test2,因此在回去的時候,要先通過test2,再通過test1,所以打印出了 -----test2-----response-----
          e. 走過test2的response以後,走到test1的response,打印出了 -----test1-----response-----
          f.順利走過自定義的中間件全部流程以後,拿着response經過wsgi返回給瀏覽器
 
第五步:總結
          a. process_request() 只有一個參數request, 這個request和視圖函數中的request是同樣的。它的返回值能夠是None也能夠是HttpResponse對象。返回值是None的話,按正常流程繼續走,交給下一個中間件處理,若是是HttpResponse對象,Django將不執行視圖函數,而將相應對象返回給瀏覽器。
          b. 多箇中間件中的 process_response() 方法是按照MIDDLEWARE中的 註冊順序倒序執行的。它有兩個參數,一個是request,一個是response,request是和process_request中同樣的對象,response是視圖函數返回的HttpResponse對象。該方法的返回值也必須是HttpResponse對象。
          c. 中間件的 process_request方法是在執行視圖函數 以前執行的。而 process_response方法是在執行視圖函數 以後執行的。
          d. 當配置多箇中間件時,會按照MIDDLEWARE中的註冊順序,也就是列表的索引值執行。 process_request是從前到後依次執行的;而process_response是從後到前依次執行的。
          e.  不一樣中間件之間傳遞的request和response都是同一個對象。(所以,若是在中間件這裏截取到request和response卻並不把它交給下一個環節,那麼後面會接收不到request和response)
 
 
瞭解了兩位守門大將以後,咱們再來認識一下宮廷守衛(process_view())吧!
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)。
它應該返回None或一個HttpResponse對象。 若是返回None,Django將繼續處理這個請求,執行任何其餘中間件的process_view方法,而後在執行相應的視圖。 若是它返回一個HttpResponse對象,Django不會調用適當的視圖函數。 它將執行中間件的process_response方法並將應用到該HttpResponse並返回結果。
第一步:寫入中間件到 mymiddleware中
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
 
class test1(MiddlewareMixin):
    def process_request(self, request):
        print('-----test1-----request-----')
        return
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):    #處理視圖的實例方法,它有五個參數
        print("-----test1-----view-----")    # 打印,用來分析執行順序
        print(view_func, view_func.__name__)    #一個是打印參數view_func傳遞的是什麼,另外打印它的__name__屬性,也就是函數名
 
class test2(MiddlewareMixin):
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test2-----view-----")    #用來與test1中的view作對比
        print(view_func, view_func.__name__)
 
第二步:運行server,查看結果並分析
-----test1-----request-----
-----test2-----request-----    # 按正序運行,先運行test1的request,再運行test2的request
-----test1-----view-----    # test2中的request運行完成以後接着運行test1中的view!
<function home at 0x02CCB468> home    而後發現view_func傳遞的是一個函數的地址,這裏函數是home函數(視圖中自定義的函數,也就是request和response傳遞的對象)
-----test2-----view-----    # test1中的view運行結束以後,接着運行test2中的view,原來view和request同樣,都是正向運行
<function home at 0x02CCB468> home    # test1中的view和test2中的view同樣,可是想一下,這個處理view實例方法是幹嗎的呢?它出如今request運行以後,在真正運行視圖函數以前,emmmm...我也不知道這哥們是作什麼的,暫且稱它爲宮廷守衛吧!
-----views-----    #這個是真正的視圖函數執行的結果
-----test2-----response-----    # 接下來response按照倒序正常運行
-----test1-----response-----
 
第三步:分析
           a. 首先運行test1類中處理request的實例方法,打印出了 -----test1-----request-----
          b. 而後運行test2類中處理request的實例方法,打印出了 -----test2-----request-----
          c. 接着運行test1類中處理view的實例方法,打印出了 -----test1-----view-----        
          d. 而後運行test2類中處理view的實例方法,打印出了 -----test2-----view-----
          e. 接着運行到視圖函數中,打印出了 -----views-----
          f. 而後從視圖函數中拿到response,開始往回走,因爲來的時候先通過test1,在通過test2,因此在回去的時候,要先通過test2,再通過test1,所以打印出了 -----test2-----response-----
          g. 走過test2的response以後,走到test1的response,打印出了 -----test1-----response-----
          h. 順利走過自定義的中間件全部流程以後,拿着response經過wsgi返回給瀏覽器
 
第四步:總結
          a. process_view方法是在 process_request以後,視圖函數以前執行的,執行順序按照MIDDLEWARE中的 註冊順序從前到後順序執行的
          b. 它返回None或一個HttpResponse對象。 若是返回None,Django將繼續處理這個請求,執行任何其餘中間件的process_view方法,而後在執行相應的視圖。 若是它返回一個HttpResponse對象,Django不會調用適當的視圖函數。 它將執行中間件的process_response方法並將應用到該HttpResponse並返回結果。
 
 
接下來要登場的是皇家守衛中..霸氣側漏大名鼎鼎的——醫療兵(processe_exception)(:D)
process_exception的我的簡介
process_exception(self, request, exception)
request是HttpRequest對象。
exception是視圖函數異常產生的Exception對象。
這個方法 只有在視圖函數中出現異常了才執行,它返回的值能夠是一個None也能夠是一個HttpResponse對象。若是是HttpResponse對象,Django將調用模板和中間件中的process_response方法,並返回給瀏覽器,不然將默認處理異常。若是返回一個None,則交給下一個中間件的process_exception方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。
第一步:寫入中間件到 mymiddleware中
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
 
class test1(MiddlewareMixin):
    def process_request(self, request):
        print('-----test1-----request-----')
        return
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test1-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):    # 處理異常的實例方法,有兩個參數
        print(exception)    # 打印exception這個參數,猜想是異常信息
        print("-----test1-----exception-----")    # 打印,分析運行順序
        return HttpResponse(str(exception))    #因爲拋出自定義異常會報錯,咱們在這裏把異常截獲並改變response
 
class test2(MiddlewareMixin):
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test2-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):    
        print(exception)    # 用來分析是否與test1中是同一個異常
        print("-----test2-----exception-----")    # 用來與test1作對比
 
第二步:因爲process_exception只有當遇到視圖函數中拋出異常時才作處理,所以咱們手動拋出一個異常
# views.py
 
from django.shortcuts import render, redirect, HttpResponse
 
# Create your views here.
def home(request):
    print('-----views-----')
    raise ValueError("自定義異常")    #拋出自定義異常,ValueError
    return HttpResponse('-----home-----')
 
第三步:運行server,查看結果並分析
-----test1-----request-----
-----test2-----request-----
-----test1-----view-----
<function home at 0x02CCB468> home
-----test2-----view-----
<function home at 0x02CCB468> home
-----views-----    # 正常運行到視圖函數中打印的部分
自定義異常    # 參數傳遞的是自定義異常
-----test2-----exception-----    # 能夠看到先運行了test2中的exception
自定義異常    # test1和test2中的異常是一個異常,能夠推斷出,test2中捕獲異常以後,把異常傳遞給了test1
-----test1-----exception-----    # process_exctption是倒序運行
-----test2-----response-----
-----test1-----response-----
 
第四步:分析
          a. 首先運行test1類中處理request的實例方法,打印出了 -----test1-----request-----
          b. 而後運行test2類中處理request的實例方法,打印出了 -----test2-----request-----
          c. 接着運行test1類中處理view的實例方法,打印出了 -----test1-----view-----        
          d. 而後運行test2類中處理view的實例方法,打印出了 -----test2-----view-----
          e. 接着運行到視圖函數中,打印出了 -----views-----
          f. 而後因爲視圖函數拋出異常,運行到test2類中處理exception的實例方法,打印出了 -----test2-----exception-----  
          g. 接着運行到test1中處理exception的實例方法,打印出了 -----test1-----exception-----  
          h. 而後從視圖函數中拿到response,開始往回走,因爲來的時候先通過test1,在通過test2,因此在回去的時候,要先通過test2,再通過test1,所以打印出了 -----test2-----response-----
          i. 走過test2的response以後,走到test1的response,打印出了 -----test1-----response-----
          j. 順利走過自定義的中間件全部流程以後,拿着response經過wsgi返回給瀏覽器
 
第五步:總結
          a.  若是視圖函數中無異常,process_exception方法不執行。
          b. 這裏醫療兵也很容易理解對吧?(:D)
 
 
介紹了以上四員大將以後,最後一個要出場的傢伙露面的機會很少(使用較少),但仍是要介紹對吧!
process_template_response的我的簡介
process_template_response(self, request, response)process_view(self, request, view_func, view_args, view_kwargs)
request是HttpRequest對象。
response是TemplateResponse對象(由視圖函數或者中間件產生)。
view_args是將傳遞給視圖的位置參數的列表。
view_kwargs是將傳遞給視圖的關鍵字參數的字典。 view_args和view_kwargs都不包含第一個視圖參數(request)。
process_template_response是在視圖函數執行完成後當即執行,可是它有一個前提條件,那就是視圖函數返回的對象有一個render()方法(或者代表該對象是一個TemplateResponse對象或等價方法)。
第一步:寫入中間件到mymiddleware中
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
 
class test1(MiddlewareMixin):
    def process_request(self, request):
        print('-----test1-----request-----')
        return
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test1-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):
        print(exception)
        print("-----test1-----exception-----")
        return HttpResponse(str(exception))
 
    def process_template_response(self, request, response):    # process_template_response實例化對象,有兩個參數
        print("-----test1-----template-----")    #打印 ,用來分析順序
        return response    #把response原路返回
 
class test2(MiddlewareMixin):
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test2-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):
        print(exception)
        print("-----test2-----exception-----")
 
    def process_template_response(self, request, response):
        print("-----test2-----template-----")    #用來與test1中的process_template_response作對比
        return response
 
第二步:因爲process_template_response的觸發須要必定的條件(視圖函數返回的對象有一個render()方法)
# views.py
 
from django.shortcuts import render, redirect, HttpResponse
 
# Create your views here.
def home(request):
    print('-----views-----')
 
    def render():
        print("-----render-----")
        return HttpResponse("home")
 
    rep = HttpResponse("OK")
    rep.render = render    # 這步是經過看源碼發現能夠在外部給它添加實例化方法
    return rep
 
第三步:運行server,查看結果並分析
-----test1-----request-----
-----test2-----request-----
-----test1-----view-----
<function home at 0x02CCB0C0> home
-----test2-----view-----
<function home at 0x02CCB0C0> home
-----views-----    # 打印函數中的views
-----test2-----template-----    # 打印了test2中的process_template_response
-----test1-----template-----    # 打印了test1中的process_template_response
-----render-----    # 有意思的地方在這裏,中間件執行完以後,又回到視圖函數中執行render中的內容
-----test2-----response-----
-----test1-----response-----
 
第四步:分析
          a. 首先運行test1類中處理request的實例方法,打印出了-----test1-----request-----
          b. 而後運行test2類中處理request的實例方法,打印出了-----test2-----request-----
          c. 接着運行test1類中處理view的實例方法,打印出了-----test1-----view-----       
          d. 而後運行test2類中處理view的實例方法,打印出了-----test2-----view-----
          e. 接着運行到視圖函數中,打印出了-----views-----
          f.  而後跳過render函數,運行test2類中的處理template_response的實例方法,打印出了 -----test2-----template-----
          g. 接着運行test1類中的處理template_response的實例方法,打印出了 -----test1-----template-----
          h. 而後回過頭去運行render函數,打印出了 -----render-----
          i. 而後從視圖函數中拿到response,開始往回走,因爲來的時候先通過test1,在通過test2,因此在回去的時候,要先通過test2,再通過test1,所以打印出了-----test2-----response-----
          j. 走過test2的response以後,走到test1的response,打印出了-----test1-----response-----
          k. 順利走過自定義的中間件全部流程以後,拿着response經過wsgi返回給瀏覽器
 
第五步:總結
          a.  視圖函數執行完以後,當即執行了中間件的process_template_response方法,順序是 倒序,先執行MD1的,在執行MD2的, 接着執行了視圖函數返回的HttpResponse對象的render方法,返回了一個新的HttpResponse對象,接着執行中間件的process_response方法。
 
 
 
3、中間件的執行順序

1.request和response兩兄弟的執行順序
請求到達中間件以後,先按照正序執行每一個註冊中間件的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方法。

 

 
2.兩員守門大將(request、response)和宮廷守衛(view)的執行順序
process_request方法都執行完後,匹配路由,找到要執行的視圖函數, 先不執行視圖函數,先執行中間件中的process_view方法,process_view方法返回None,繼續按順序執行,全部process_view方法執行完後執行視圖函數。
假如 中間件3 的process_view方法返回了HttpResponse對象則4,5,6的process_view以及視圖函數都不執行,直接從最後一箇中間件,也就是中間件6的process_response方法開始倒序執行。
 

 

3.那麼醫療兵(exception)和隱者(template)與它們之間又有什麼關係呢?
它們兩個須要必定的觸發條件: exception須要視圖函數拋出一個異常;template須要視圖函數返回的對象中有一個render()方法。
a. 從下圖能夠看出,request若是返回none,則按照正常順序從上到下(註冊的順序)依次執行;
b. 若是在某個中間件中的request中出現了HttpResponse,那麼就從這個中間件的處理response的方法倒序往回執行;
c. request和view都是返回None才正常執行,而response是返回response才正常執行;
d. 若是某個中間件的處理視圖的實例化方法返回了HttpResponse, 那麼就再也不執行後續中間件的處理view的方法,而是直接走處處理response,按照倒序往回執行
 

 

a. 下面箭頭向下的有request和view,說明只有它們兩個是從上往下正常執行的;
b. template和response用了虛線,表示二者的觸發須要必定的條件;

 

 
4.對比一下皇家守衛團的成員們!
 
執行順序
什麼時候執行
返回值
process_request
按照註冊順序
請求從wsgi拿到以後
返回None,繼續執行後續的中間件的process_request方法
返回response , 不執行後續的中間件的process_request方法
process_response
按照註冊順序的倒序
請求有響應的時候
必須返回一個response對象
process_view
按照註冊順序
在urls.py中找到對應關係以後 
在執行真正的視圖函數以前
返回None,繼續執行後續的中間件的process_view方法
返回response,不執行後續中間件的process_view方法,
從最後一個process_response返回
process_exception
按照註冊順序的倒序
視圖函數中拋出異常的時候才執行
返回None,繼續執行後續中間件的process_exception
返回response,
process_template_response
按照註冊順序的倒序
視圖函數執行完,
在執行視圖函數返回的響應對象的render方法以前
返回None,繼續執行後續中間件的process_exception
返回response,
    
 
5.接下來讓咱們來完整的看一下Django的流程吧!
相關文章
相關標籤/搜索