Django - 中間件

 

 

前戲:html

前面的內容已經學會了給視圖函數加裝飾器來判斷是用戶是否登陸,把沒有登陸的用戶請求跳轉到登陸頁面。咱們經過給幾個特定視圖函數加裝飾器實現了這個需求。可是之後添加的視圖函數可能也須要加上裝飾器,這樣是否是稍微有點繁瑣。django

學完今天的內容以後呢,咱們就能夠用更適宜的方式來實現相似給全部請求都作相同操做的功能了。瀏覽器

 

1、中間件介紹

什麼是中間件?服務器

官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局範圍內改變Django的輸入和輸出。每一箇中間件組件都負責作一些特定的功能。session

可是因爲其影響的是全局,因此須要謹慎使用,使用不當會影響性能。app

說的直白一點中間件是幫助咱們在視圖函數執行以前和執行以後均可以作一些額外的操做,它本質上就是一個自定義類,類中定義了幾個方法,Django框架會在請求的特定的時間去執行這些方法。框架

咱們一直都在使用中間件,只是沒有注意到而已,打開Django項目的Settings.py文件,看到下圖的MIDDLEWARE配置項。socket

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.CustomerMiddleware1',
    'app01.my_middlewares.CustomerMiddleware2',

    # 用戶登陸認證中間件
    'app01.my_middlewares.AuthMiddleware',
]

MIDDLEWARE配置項是一個列表,列表中是一個個字符串,這些字符串實際上是一個個類,也就是一個個中間件。函數

咱們以前已經接觸過一個csrf相關的中間件了?咱們一開始讓你們把他註釋掉,再提交post請求的時候,就不會被forbidden了,後來學會使用csrf_token以後就再也不註釋這個中間件了。post

那接下來就學習中間件中的方法以及這些方法何時被執行。

 

  每個中間件都有具體的功能。

    django默認7箇中間件,其實就是一個類。

    django請求週期

接收到了socket,拿到了請求信息,wsgire作了一個封裝request的操做(解析數據,解析成一個request的對象)----------------->>>會進入中間件,執行每個中間件的process_request,它是依次執行的。------>

路由層作分發,分發完了交給視圖進行處理,視圖要響應一個值,若是是字符串就原路返回了,它返回的時候是依次執行process_response這個方法 走出中間件 ---->

wsgiref封裝了數據,(http協議必須按照響應體的響應格式進行發,若是不按照那個格式發瀏覽器沒法解析;wsgiref封裝協議,響應首行、響應體、響應體)

把請求體交給瀏覽器----》拿到響應體瀏覽器解析爲一個頁面給用戶看。

中間件無論你訪問的請求路徑是什麼,只要請求訪問的是這個服務器就必然會經過這個中間件來的時候通過process_response,回去的時候通過process_request,屬於全局性邏輯處理。

 

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對象,則直接將該對象返回給用戶。

上述截圖中的中間件都是django中的,咱們也能夠本身定義一箇中間件,咱們能夠本身寫一個類,可是必須繼承MiddlewareMixin

須要導入

from django.utils.deprecation import MiddlewareMixin

 

3、process_request

process_request有一個參數,就是request,這個request和視圖函數中的request是同樣的。

它的返回值能夠是None也能夠是HttpResponse對象。返回值是None的話,按正常流程繼續走,交給下一個中間件處理,若是是HttpResponse對象,Django將不執行視圖函數,而將相應對象返回給瀏覽器。

咱們來看看多箇中間件時,Django是如何執行其中的process_request方法的。

自定義中間件示例:

from django.utils.deprecation import MiddlewareMixin
class CustomerMiddleware1(MiddlewareMixin): #讓它繼承下這個類
def process_request(self, request): print('CustomerMiddleware1 process_request') #默認返回None;而後好比你訪問index頁面,它就會執行這句話了,走了precess_request
class CustomerMiddleware2(MiddlewareMixin):
def process_request(self, request): print('CustomerMiddleware2 process_request')
'app01.my_middlewares.CustomerMiddleware',  #在settins裏邊的MIDDLEWARE把它加進去。
'app01.my_middlewares.CustomerMiddleware2',

#這兩個中間件裏邊的process_request依次執行; 它是全局的邏輯處理

打印:

CustomerMiddleware1 process_request
CustomerMiddleware2 process_request
index  #先走中間件,再進視圖函數打印它

把CustomerMiddleware1和CustomerMiddleware2的位置調換一下,再訪問一個視圖,會發現終端中打印的內容以下:

CustomerMiddleware2 process_request
CustomerMiddleware1 process_request
index  #先走中間件,再進視圖函數打印它

看結果咱們知道:視圖函數仍是最後執行的,CustomerMiddleware1比CustomerMiddleware2先執行本身的process_request方法。

在打印一下兩個自定義中間件中process_request方法中的request參數,會發現它們是同一個對象。

由此總結一下:

  1. 中間件的process_request方法是在執行視圖函數以前執行的。
  2. 當配置多箇中間件時,會按照MIDDLEWARE中的註冊順序,也就是列表的索引值,從前到後依次執行的。
  3. 不一樣中間件之間傳遞的request都是同一個對象

多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。

中間件的做用:

大部分視圖要作的工做,咱們能夠放到中間件裏邊去,好比關於session的驗證:好像視圖函數必須是在登陸以後才能看這個頁面,必須在每一個視圖函數裏邊加判斷或者加裝飾器,二三十個視圖函數都掛一個裝飾器login_request,若是把校驗放到中間件裏邊去,在這裏邊定義一次,全部的請求都進來以後都會校驗它是否登陸了 

 

4、process_response

它有兩個參數,一個是request,一個是response,request就是上述例子中同樣的對象,response是視圖函數返回的HttpResponse對象。該方法的返回值也必須是HttpResponse對象。

給上述的CustomerMiddleware1和CustomerMiddleware2加上process_response方法

from django.utils.deprecation import MiddlewareMixin
class CustomerMiddleware1(MiddlewareMixin):

    def process_request(self, request):
        print('CustomerMiddleware1 process_request')

    def process_response(self, request,response): #response就是HttpResponse(INDEX)相應體對象
        print('CustomerMiddleware1 process_response')
        return response  #它必需要有返回值,就像接力棒一層層傳給別人;在process_request裏邊必需要寫返回值。

class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print('CustomerMiddleware2 process_request')

    def process_response(self, request,response):
        print('CustomerMiddleware2 process_response')
        return response

打印:

CustomerMiddleware1 process_request
CustomerMiddleware2 process_request
index
CustomerMiddleware2 process_response
CustomerMiddleware1 process_response

看結果可知:

process_response方法是在視圖函數以後執行的,而且順序是CustomerMiddleware2比CustomerMiddleware1先執行。

多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。

 

若是非要在process_request加個返回值

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class CustomerMiddleware(MiddlewareMixin):

    def process_request(self, request):
        print('CustomerMiddleware1 process_request')
        return HttpResponse('forbidden..')

    def process_response(self, request,response): #response就是HttpResponse(INDEX)相應體對象
        print('CustomerMiddleware1 process_response')
        return response  #它必需要有返回值,就像接力棒一層層傳給別人

class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print('CustomerMiddleware2 process_request')

    def process_response(self, request,response):
        print('CustomerMiddleware2 process_response')
        return response

打印:

CustomerMiddleware1 process_request
CustomerMiddleware1 process_response

#訪問http://127.0.0.1:8000/index/打印的是forbidden..,

沒有執行中間件2的內容,也沒有執行視圖。

   url請求中間件1的process_request的時候,一旦加了返回值,直接把響應體交給本身的process_response,交給瀏覽器。一會兒給攔截了。

   應用場景:攔截,你的ip頻率過高;session進行校驗的時候沒有登陸也給你攔截下來。

5、process_view

process_view(self, request, callback, callback_args, callback_kwargs)

該方法有四個參數

request是HttpRequest對象。

callback是Django即將使用的視圖函數。 (它是實際的函數對象,而不是函數的名稱做爲字符串。)

callback_args是將傳遞給視圖的位置參數的列表.

callback_kwargs是將傳遞給視圖的關鍵字參數的字典。 callback_args和callback_kwargs都不包含第一個視圖參數(request)。

Django會在調用視圖函數以前調用process_view方法。

它應該返回None或一個HttpResponse對象。 若是返回None,Django將繼續處理這個請求,執行任何其餘中間件的process_view方法,而後在執行相應的視圖。 若是它返回一個HttpResponse對象,Django不會調用適當的視圖函數。 它將執行中間件的process_response方法並將應用到該HttpResponse並返回結果。

加了process_view以後打印執行的順序:

CustomerMiddleware1 process_request
CustomerMiddleware2 process_request
CustomerMiddleware1:process_view
CustomerMiddleware2:process_view
index
CustomerMiddleware2 process_response
CustomerMiddleware1 process_response

 

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class CustomerMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print('CustomerMiddleware1 process_request')
        #return HttpResponse('forbidden..')
    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomerMiddleware1:process_view")

    def process_response(self, request,response): #response就是HttpResponse(INDEX)相應體對象
        print('CustomeerMiddleware1 process_response')
        return response  #它必需要有返回值,就像接力棒一層層傳給別人

class CustomerMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomerMiddleware2 process_request')
    def process_response(self, request,response):
        print('CustomeerMiddleware2 process_response')
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        #print("===>",callback) #===> <function index at 0x0000000003DE11E0> 視圖函數
        #在進入到2的視圖函數的時候,把響應體體返回了 123,那麼視圖函數就不執行了,接着往下執行process_response
        print("===>",callback(callback_args)) #不走視圖函數了我提早拿到它的響應結果

        print("CustomerMiddleware2:process_view")
        ret = callback(callback_args)      ###能夠進行攔截。
        return ret  #把它的結果返回了就能夠拿到視圖函數裏邊的index了
        #return HttpResponse('123') # 沒有走index,返回123

打印:

上邊的執行結果
CustomerMiddleware1 process_request
CustomerMiddleware2 process_request
CustomerMiddleware1:process_view
===> <HttpResponse status_code=200, "text/html; charset=utf-8">
CustomerMiddleware2:process_view
index
CustomerMiddleware2 process_response
CustomerMiddleware1 process_response

 

6、process_exception

process_exception(self, request, exception)

該方法兩個參數:

一個HttpRequest對象

一個exception是視圖函數異常產生的Exception對象。

這個方法只有在視圖函數中出現異常了才執行,它返回的值能夠是一個None也能夠是一個HttpResponse對象。若是是HttpResponse對象,Django將調用模板和中間件中的process_response方法,並返回給瀏覽器,不然將默認處理異常。若是返回一個None,則交給下一個中間件的process_exception方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。

 給CustomerMiddleware1和MCustomerMiddleware2添加上這個方法:

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class CustomerMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print('CustomerMiddleware1 process_request')
        #return HttpResponse('forbidden..')
    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomerMiddleware1:process_view")

    def process_response(self, request,response): #response就是HttpResponse(INDEX)相應體對象
        print('CustomerMiddleware1 process_response')
        return response  #它必需要有返回值,就像接力棒一層層傳給別人
    def process_exception(self, request, exception):
        print('CustomeerMiddleware1 process_exception')

class CustomerMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomerMiddleware2 process_request')
    def process_response(self, request,response):
        print('CustomerMiddleware2 process_response')
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        #print("===>",callback) #===> <function index at 0x0000000003DE11E0> 視圖函數
        #在進入到2的視圖函數的時候,把相應體返回了 123,那麼視圖函數就不執行了,接着往下執行process_response
        #print("===>",callback(callback_args))

        print("CustomerMiddleware2:process_view")
        # ret = callback(callback_args)
        # return ret  #把它的結果返回了就能夠拿到index了
        #return HttpResponse('123')
    def process_exception(self, request, exception):
        print('CustomeerMiddleware2 process_exception')
        return HttpResponse(exception)  #一旦有一個return了,後邊1的process_exception就不執行了;把它的錯誤信息給返回了唄

打印:

CustomerMiddleware1 process_request
CustomerMiddleware2 process_request
CustomerMiddleware1:process_view
CustomerMiddleware2:process_view
index
CustomeerMiddleware2 process_exception #直接檢測完,把它的錯誤捕捉到交給響應體返回了,就不執行1的process_exception了
CustomeerMiddleware2 process_response
CustomeerMiddleware1 process_response

若是把return寫到1裏邊,2不要,return HttpResponse(exception) 

CustomerMiddleware1 process_request
CustomerMiddleware2 process_request
CustomerMiddleware1:process_view
CustomerMiddleware2:process_view
index
CustomeerMiddleware2 process_exception
CustomeerMiddleware1 process_exception
CustomeerMiddleware2 process_response
CustomeerMiddleware1 process_response

 

7、經過中間件進行登陸驗證

若是用戶訪問的是login視圖(放過)

若是訪問其餘視圖,須要檢測是否是有session認證,已經有了放行,沒有返回login,這樣就免得在多個視圖函數上寫裝飾器了!

  my_middlewares.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
from authDemo import settings

class AuthMiddleware(MiddlewareMixin):  #認證
    def process_request(self, request):
        white_list = settings.WHITE_LIST #先拿到那個白名單
        if request.path in white_list:
            return None                  #至關於經過校驗
        if not request.user.is_authenticated:
            return redirect("/login/")

  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',
    'app01.my_middlewares.AuthMiddleware', #把它加上去

]

LOGIN_URL = "/login/"  #跳轉到哪裏由我本身決定;

WHITE_LIST = ["/login/","/reg/","logout/"] #設置下白名單

 

8、中間件的執行流程

上一部分,咱們瞭解了中間件中的5個方法,它們的參數、返回值以及何時執行,如今總結一下中間件的執行流程。

請求到達中間件以後,先按照正序執行每一個註冊中間件的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方法。

process_request方法都執行完後,匹配路由,找到要執行的視圖函數,先不執行視圖函數,先執行中間件中的process_view方法,process_view方法返回None,繼續按順序執行,全部process_view方法執行完後執行視圖函數。加入中間件3 的process_view方法返回了HttpResponse對象,則4,5,6的process_view以及視圖函數都不執行,直接從最後一箇中間件,也就是中間件6的process_response方法開始倒序執行。

process_template_response和process_exception兩個方法的觸發是有條件的,執行順序也是倒序。總結全部的執行流程以下:

 

 

Django請求流程圖

 

相關文章
相關標籤/搜索