Django事務、鎖和中間件

1、事務和鎖

javascript

mysql:  select * from book where id=1 for update;數據庫中行級鎖的用法

begin;  start transaction;
    select * from t1 where id=1 for update;
commit

rollback;


django orm
    models.Book.objects.select_for_update().filter(id=1)

事務html

  1. Django是作事務處理
django1.8版本以前是有不少種添加事務的方式的,中間件的形式(全局的)、函數裝飾器的形式,上下文管理器的形式等,可是不少方法都在1.8版以後給更新了,下面咱們只說最新的:
  1. 1 全局開啓

  在Web應用中,經常使用的事務處理方式是將每一個請求都包裹在一個事務中。這個功能使用起來很是簡單,你只須要將它的配置項ATOMIC_REQUESTS設置爲True。java

  它是這樣工做的:當有請求過來時,Django會在調用視圖方法前開啓一個事務。若是請求被正確處理並正確返回告終果,Django就會提交該事務。不然,Django會回滾該事務。python

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxshop',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        'OPTIONS': {
            "init_command": "SET default_storage_engine='INNODB'",
       #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置開啓嚴格sql模式
        }
        "ATOMIC_REQUESTS": True, #全局開啓事務,綁定的是http請求響應整個過程       "AUTOCOMMIT":False, #全局取消自動提交,慎用
    },  
    'other':{    
        'ENGINE': 'django.db.backends.mysql',......} #還能夠配置其餘數據庫
}

  上面這種方式是統一一個http請求對應的全部sql都放在一個事務中執行(要麼全部都成功,要麼全部都失敗)。是全局性的配置, 若是要對某個http請求放水(而後自定義事務),能夠用non_atomic_requests修飾器,那麼他就不受事務的管控了。mysql

from django.db import transaction

@transaction.non_atomic_requests
def my_view(request):
    do_stuff()

@transaction.non_atomic_requests(using='other')
def my_other_view(request):
    do_stuff_on_the_other_database()

  可是Django 文檔中說,不推薦這麼作。由於若是將事務跟 HTTP 請求綁定到一塊兒的時,然而view 是依賴於應用程序對數據庫的查詢語句效率和數據庫當前的鎖競爭狀況。當流量上來的時候,性能會有影響,知道一下就好了sql

  因此推薦用下面這種方式,經過 transaction.atomic 來更加明確的控制事務。atomic容許咱們在執行代碼塊時,在數據庫層面提供原子性保證。 若是代碼塊成功完成, 相應的變化會被提交到數據庫進行commit;若是執行期間遇到異常,則會將該段代碼所涉及的全部更改回滾。數據庫

  1. 2 局部使用事務

  atomic(using=None, savepoint=True)[source] ,參數:using='other',就是當你操做其餘數據庫的時候,這個事務才生效,看上面咱們的數據庫配置,除了default,還有一個other,默認的是default。savepoint的意思是開啓事務保存點,推薦看一下我數據庫博客裏面的事務部分關於保存點的解釋。django

​ 原子性是數據庫事務的一個屬性。使用atomic,咱們就能夠建立一個具有原子性的代碼塊。一旦代碼塊正常運行完畢,全部的修改會被提交到數據庫。反之,若是有異常,更改會被回滾。瀏覽器

  被atomic管理起來的代碼塊還能夠內嵌到方法中。這樣的話,即使內部代碼塊正常運行,若是外部代碼塊拋出異常的話,它也沒有辦法把它的修改提交到數據庫中。安全

  用法1:給函數作裝飾器來使用 

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

  用法2:做爲上下文管理器來使用,其實就是設置事務的保存點

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():   #保存點
        # This code executes inside a transaction.
        do_more_stuff()

    do_other_stuff()

  一旦把atomic代碼塊放到try/except中,完整性錯誤就會被天然的處理掉了,好比下面這個例子:

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

  用法3:還能夠嵌套使用,函數的事務嵌套上下文管理器的事務,上下文管理器的事務嵌套上下文管理器的事務等。下面的是函數嵌套上下文的例子:

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()       #other_task()  #還要注意一點,若是你在事務裏面寫了別的操做,只有這些操做所有完成以後,事務纔會commit,也就是說,若是你這個任務是查詢上面更改的數據表裏面的數據,那麼看到的仍是事務提交以前的數據。
    except IntegrityError:
        handle_exception()

    add_children()

  這個例子中,即便generate_relationships()中的代碼打破了數據完整性約束,你仍然能夠在add_children()中執行數據庫操做,而且create_parent()產生的更改也有效。須要注意的是,在調用handle_exception()以前,generate_relationships()中的修改就已經被安全的回滾了。所以,若是有須要,你照樣能夠在異常處理函數中操做數據庫。

儘可能不要在atomic代碼塊中捕獲異常

  由於當atomic塊中的代碼執行完的時候,Django會根據代碼正常運行來執行相應的提交或者回滾操做。若是在atomic代碼塊裏面捕捉並處理了異常,就有可能隱蓋代碼自己的錯誤,從而可能會有一些意料以外的不愉快事情發生。

  擔憂主要集中在DatabaseError和它的子類(如IntegrityError)。若是這種異常真的發生了,事務就會被破壞掉,而Django會在代碼運行完後執行回滾操做。若是你試圖在回滾前執行一些數據庫操做,Django會拋出TransactionManagementError。一般你會在一個ORM相關的信號處理器拋出異常時遇到這個行爲。

捕獲異常的正確方式正如上面atomic代碼塊所示。若是有必要,添加額外的atomic代碼塊來作這件事情,也就是事務嵌套。這麼作的好處是:當異常發生時,它能明確地告訴你那些操做須要回滾,而那些是不須要的。

​ 爲了保證原子性,atomic還禁止了一些API。像試圖提交、回滾事務,以及改變數據庫鏈接的自動提交狀態這些操做,在atomic代碼塊中都是不予許的,不然就會拋出異常。

​ 下面是Django的事務管理代碼:

  • 進入最外層atomic代碼塊時開啓一個事務;
  • 進入內部atomic代碼塊時建立保存點;
  • 退出內部atomic時釋放或回滾事務;注意若是有嵌套,內層的事務也是不會提交的,能夠釋放(正常結束)或者回滾
  • 退出最外層atomic代碼塊時提交或者回滾事務;

    你能夠將保存點參數設置成False來禁止內部代碼塊建立保存點。若是發生了異常,Django在退出第一個父塊的時候執行回滾,若是存在保存點,將回滾到這個保存點的位置,不然就是回滾到最外層的代碼塊。外層事務仍然可以保證原子性。然而,這個選項應該僅僅用於保存點開銷較大的時候。畢竟它有個缺點:會破壞上文描述的錯誤處理機制。

  注意:transaction只對數據庫層的操做進行事務管理,不能理解爲python操做的事務管理

def example_view(request):
    tag = False
    with transaction.atomic():
        tag = True
        change_obj() # 修改對象變量
        obj.save()
        raise DataError
    print("tag = ",tag) #結果是True,也就是說在事務中的python變量賦值,即使是事務回滾了,這個賦值也是成功的

還要注意:若是你配置了全局的事務,它和局部事務可能會產生衝突,你可能會發現你局部的事務完成以後,若是你的函數裏面其餘的sql除了問題,也就是沒在這個上下文管理器的局部事務包裹範圍內的函數裏面的其餘的sql出現了問題,你的局部事務也是提交不上的,由於全局會回滾這個請求和響應所涉及到的全部的sql,因此仍是建議之後的項目儘可能不要配置全局的事務,經過局部事務來搞定,固然了,看大家的業務場景。

  transaction的其餘方法

@transaction.atomic
def viewfunc(request):

  a.save()
  # open transaction now contains a.save()
  sid = transaction.savepoint()  #建立保存點

  b.save()
  # open transaction now contains a.save() and b.save()

  if want_to_keep_b:
      transaction.savepoint_commit(sid) #提交保存點
      # open transaction still contains a.save() and b.save()
  else:
      transaction.savepoint_rollback(sid)  #回滾保存點
      # open transaction now contains only a.save()

  transaction.commit() #手動提交事務,默認是自動提交的,也就是說若是你沒有設置取消自動提交,那麼這句話不用寫,若是你配置了那個AUTOCOMMIT=False,那麼就須要本身手動進行提交。[![複製代碼](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);

  爲保證事務的隔離性,咱們還能夠結合上面的鎖來實現,也就是說在事務裏面的查詢語句,我們使用select_for_update顯示的加鎖方式來保證隔離性,事務結束後纔會釋放這個鎖,例如:(瞭解)

@transaction.atomic ## 輕鬆開啓事務
def handle(self):
    ## 測試是否存在此用戶
    try:
        ## 鎖定被查詢行直到事務結束
        user = 
    User.objects.select_for_update().get(open_id=self.user.open_id)
        #other sql 語句
    except User.DoesNotExist:
        raise BaseError(-1, 'User does not exist.')

  經過Django外部的python腳原本測試一下事務:

import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
    import django
    django.setup()

    import datetime
    from app01 import models

    try:
        from django.db import transaction
        with transaction.atomic():
            new_publisher = models.Publisher.objects.create(name="火星出版社")
            models.Book.objects.create(title="橘子物語", publish_date=datetime.date.today(), publisher_id=10)  # 指定一個不存在的出版社id
    except Exception as e:
        print(str(e))

  下面再說一些設置事務的小原則吧:

    1.保持事務短小
    2.儘可能避免事務中rollback
    3.儘可能避免savepoint
    4.默認狀況下,依賴於悲觀鎖
    5.爲吞吐量要求苛刻的事務考慮樂觀鎖
    6.顯示聲明打開事務
    7.鎖的行越少越好,鎖的時間越短越好

from django.db import transaction

@transaction.atomic
def index(request):
    
    ...


def index(request):
    ...
    with transaction.atomic():
        xxxx
    ...

2、中間件(用更適宜的方式來實現相似給全部請求都作相同操做的功能)

  1. 1 中間件是介於request與response處理之間的一道處理過程,相對比較輕量級,而且在全局上改變django的輸入與輸出,改變的是全局,用很差會影響性能,慎用。

  2. 中間件顧名思義,是介於request與response處理之間的一道處理過程,相對比較輕量級,而且在全局上改變django的輸入與輸出。由於改變的是全局,因此須要謹慎實用,用很差會影響到性能。

    Django的中間件的定義:

    Middleware is a framework of hooks into Django’s request/response processing. <br>It’s a light, low-level 「plugin」 system for globally altering Django’s input or output.

      若是你想修改請求,例如被傳送到view中的HttpRequest對象。 或者你想修改view返回的HttpResponse對象,這些均可以經過中間件來實現。

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

      咱們一直都在使用中間件,只是沒有注意到而已,打開Django項目的Settings.py文件,看到下面的MIDDLEWARE配置項,django默認自帶的一些中間件:

    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',
    ]

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

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

    請求生命週期

img

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)
  1. 自定義中間件

    中間件能夠定義五個方法,分別是:(主要的是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對象,則直接將該對象返回給用戶。

      當用戶發起請求的時候會依次通過全部的的中間件,這個時候的請求時process_request,最後到達views的函數中,views函數處理後,在依次穿過中間件,這個時候是process_response,最後返回給請求者。

    1570794467084

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

    1. 自定義一箇中間件示例:

      目錄:

        在項目中建立一個包,隨便起名字,通常都放在一個叫作utils的包裏面,表示一個公用的組件,建立一個py文件,隨便起名字,例如叫作:middlewares.py,內容以下

    from django.utils.deprecation import MiddlewareMixin
    
    class MD1(MiddlewareMixin):
        #自定義中間件,不是必需要有下面這兩個方法,有request方法說明請求來了要處理,有response方法說明響應出去時須要處理,不是非要寫這兩個方法,若是你沒寫process_response方法,那麼會一層一層的往上找,哪一個中間件有process_response方法就將返回對象給哪一個中間件
        def process_request(self, request):
            print("MD1裏面的 process_request")
    
        def process_response(self, request, response):
            print("MD1裏面的 process_response")
            return response

    process_request

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

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

    ​ Django執行process_request方法

    from django.utils.deprecation import MiddlewareMixin
    class MD1(MiddlewareMixin):
        def process_request(self, request):
            print("MD1裏面的 process_request")
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2裏面的 process_request")
            pass

    ​ 在settings.py的MIDDLEWARE配置項中註冊上述兩個自定義中間件:

    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',
        'middlewares.MD1',  # 自定義中間件MD1,這個寫的是你項目路徑下的一個路徑,例如,若是你放在項目下,文件夾名成爲utils,那麼這裏應該寫utils.middlewares.MD1
        'middlewares.MD2'  # 自定義中間件MD2
    ]

    此時,咱們訪問一個視圖,會發現終端中打印以下內容:

    MD1裏面的 process_request
    MD2裏面的 process_requestapp01 中的 index視圖

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

    MD2裏面的 process_request
    MD1裏面的 process_requestapp01 中的 index視圖

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

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

          由此總結一下:

            中間件的process_request方法是在執行視圖函數以前執行的。

            當配置多箇中間件時,會按照MIDDLEWARE中的註冊順序,也就是列表的索引值,從前到後依次執行的。

            不一樣中間件之間傳遞的request都是同一個對象

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

        process_response

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

          給上述的M1和M2加上process_response方法:

    from django.utils.deprecation import MiddlewareMixin
    
    
    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1裏面的 process_request")
            #沒必要須寫return值
        def process_response(self, request, response):#request和response兩個參數必須有,名字隨便取
            print("MD1裏面的 process_response")        #print(response.__dict__['_container'][0].decode('utf-8')) #查看響應體裏面的內容的方法,或者直接使用response.content也能夠看到響應體裏面的內容,因爲response是個變量,直接點擊看源碼是看不到的,你打印type(response)發現是HttpResponse對象,查看這個對象的源碼就知道有什麼方法能夠用了。
         return response  #必須有返回值,寫return response  ,這個response就像一個接力棒同樣
            #return HttpResponse('瞎搞') ,若是你寫了這個,那麼你視圖返回過來的內容就被它給替代了
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2裏面的 process_request")
            pass
    
        def process_response(self, request, response): #request和response兩個參數必需要有,名字隨便取
            print("MD2裏面的 process_response") 
            return response  #必須返回response,否則你上層的中間件就沒有拿到httpresponse對象,就會報錯

    ​ 訪問一個視圖,看一下終端的輸出:

    MD2裏面的 process_request
    MD1裏面的 process_request
    app01 中的 index視圖
    MD1裏面的 process_response
    MD2裏面的 process_response

      看結果可知:

      process_response方法是在視圖函數以後執行的,而且順序是MD1比MD2先執行。(此時settings.py中 MD2比MD1先註冊)

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

      再看一個例子:

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print("Md1請求")     #process_request方法裏面不寫返回值,默認也是返回None,若是你本身寫了return None,也是同樣的效果,不會中斷你的請求,可是若是你return 的一個httpresponse對象,那麼就會在這個方法中斷你的請求,直接返回給用戶,這就成了非正常的流程了        #而且,若是你在這裏return了httpresponse對象,那麼會從你這個中間件類中的process_response方法開始執行返回操做,因此這個類裏面只要有process_response方法,確定會執行
    
        def process_response(self,request,response):
            print("Md1返回")
            return response
    
    class Md2(MiddlewareMixin):
    
        def process_request(self,request):
            print("Md2請求")
            #return HttpResponse("Md2中斷")
        def process_response(self,request,response):
            print("Md2返回")
            return response

        結果:

    Md1請求
    Md2請求
    view函數...
    Md2返回
    Md1返回

      注意:若是當請求到達請求2的時候直接不符合條件返回,即return HttpResponse("Md2中斷")**,程序將把請求直接發給中間件2返回,而後依次返回到請求者,結果以下:

      返回Md2中斷的頁面,後臺打印以下:

    Md1請求
    Md2請求
    Md2返回
    Md1返回

      流程圖以下:**

        img

      以前咱們作的cookie認證,都是經過在函數上面加裝飾器搞的,比較麻煩,看看中間件怎麼搞,若是寫的是session認證的,你必須放在django自帶的session中間件的下面,因此自定義中間以後,你須要注意你的中間件的擺放順序。

    class M1(MiddlewareMixin):
    
        def process_request(self,request):
    
            #設置路徑白名單,只要訪問的是login登錄路徑,就不作這個cookie認證
            if request.path not in [reverse('login'),]:
                print('我是M1中間件') #客戶端IP地址
                # return HttpResponse('sorry,沒有經過個人M1中間件')
                is_login = request.COOKIES.get('is_login', False)
    
                if is_login:
                    pass
                else:
                    # return render(request,'login.html')
                    return redirect(reverse('login'))
            else:
                return None #別忘了return None,或者直接寫個pass
    
        def process_response(self,request,response):
    
            print('M1響應部分')
            # print(response.__dict__['_container'][0].decode('utf-8'))
            return response
            # return HttpResponse('瞎搞')

    ​ 練習:嘗試一下經過中間件來控制用戶的訪問次數,讓用戶在一分鐘以內不能訪問個人網站超過20次。

    ​ 後面要學的方法不經常使用,可是你們最好也要知道。

      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)。

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

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

            img

          給MD1和MD2添加process_view方法:

    from django.utils.deprecation import MiddlewareMixin
    
    
    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1裏面的 process_request")
    
        def process_response(self, request, response):
            print("MD1裏面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__) #就是url映射到的那個視圖函數,也就是說每一箇中間件的這個process_view已經提早拿到了要執行的那個視圖函數        #ret = view_func(request) #提早執行視圖函數,不用到了上圖的試圖函數的位置再執行,若是你視圖函數有參數的話,能夠這麼寫 view_func(request,view_args,view_kwargs) 
            #return ret  #直接就在MD1中間件這裏這個類的process_response給返回了,就不會去找到視圖函數裏面的這個函數去執行了。
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2裏面的 process_request")
            pass
    
        def process_response(self, request, response):
            print("MD2裏面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD2 中的process_view")
            print(view_func, view_func.__name__)

      訪問index視圖函數,看一下輸出結果:

    MD2裏面的 process_request
    MD1裏面的 process_request
    --------------------------------------------------------------------------------
    MD2 中的process_view
    <function index at 0x000001DE68317488> index
    --------------------------------------------------------------------------------
    MD1 中的process_view
    <function index at 0x000001DE68317488> index
    app01 中的 index視圖
    MD1裏面的 process_response
    MD2裏面的 process_response

      process_view方法是在process_request以後,reprocess_response以前,視圖函數以前執行的,執行順序按照MIDDLEWARE中的註冊順序從前到後順序執行的

            img

        process_exception

          process_exception(self, request, exception)

          該方法兩個參數:

          一個HttpRequest對象

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

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

            img

          給MD1和MD2添加上這個方法:

    from django.utils.deprecation import MiddlewareMixin
    
    
    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1裏面的 process_request")
    
        def process_response(self, request, response):
            print("MD1裏面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD1 中的process_exception")
    
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2裏面的 process_request")
            pass
    
        def process_response(self, request, response):
            print("MD2裏面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD2 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD2 中的process_exception")

    ​ 若是視圖函數中無異常,process_exception方法不執行。

      想辦法,在視圖函數中拋出一個異常:

    def index(request):
        print("app01 中的 index視圖")
        raise ValueError("呵呵")
        return HttpResponse("O98K")

      在MD1的process_exception中返回一個響應對象:

    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1裏面的 process_request")
    
        def process_response(self, request, response):
            print("MD1裏面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD1 中的process_exception")
            return HttpResponse(str(exception))  # 返回一個響應對象

    ​ 看輸出結果:

    MD2裏面的 process_request
    MD1裏面的 process_request
    --------------------------------------------------------------------------------
    MD2 中的process_view
    <function index at 0x0000022C09727488> index
    --------------------------------------------------------------------------------
    MD1 中的process_view
    <function index at 0x0000022C09727488> index
    app01 中的 index視圖
    呵呵
    MD1 中的process_exception
    MD1裏面的 process_response
    MD2裏面的 process_response

            img

       注意,這裏並無執行MD2的process_exception方法,由於MD1中的process_exception方法直接返回了一個響應象。

       process_template_response(用的比較少)**

    ​ process_template_response(self, request, response)

    ​ 它的參數,一個HttpRequest對象,response是TemplateResponse對象(由視圖函數或者中間件產生)。 process_template_response是在視圖函數執行完成後當即執行,可是它有一個前提條件,那就是視圖函數返回的對象有一個render()方法(或者代表該對象是一個TemplateResponse對象或等價方法)。

    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1裏面的 process_request")
    
        def process_response(self, request, response):
            print("MD1裏面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD1 中的process_exception")
            return HttpResponse(str(exception))
    
        def process_template_response(self, request, response):
            print("MD1 中的process_template_response")
            return response
    
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2裏面的 process_request")
            pass
    
        def process_response(self, request, response):
            print("MD2裏面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD2 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD2 中的process_exception")
    
        def process_template_response(self, request, response):
            print("MD2 中的process_template_response")
            return response

          views.py中:

    def index(request):
        print("app01 中的 index視圖")
      #raise ValueError('出錯啦') 
        def render():
            print("in index/render")          #raise ValueError('出錯啦') #至於render函數中報錯了,那麼會先執行process_template_response方法,而後執行process_exception方法,若是是在render方法外面報錯了,那麼就不會執行這個process_template_response方法了。
            return HttpResponse("O98K") #返回的將是這個新的對象
        rep = HttpResponse("OK")
        rep.render = render
        return rep

          訪問index視圖,終端輸出的結果:

    MD2裏面的 process_request
    MD1裏面的 process_request
    --------------------------------------------------------------------------------
    MD2 中的process_view
    <function index at 0x000001C111B97488> index
    --------------------------------------------------------------------------------
    MD1 中的process_view
    <function index at 0x000001C111B97488> index
    app01 中的 index視圖
    MD2 中的process_template_response
    MD1 中的process_template_response
    in index/render
    MD1裏面的 process_response
    MD2裏面的 process_response

          從結果看出:

          視圖函數執行完以後,當即執行了中間件的process_template_response方法,順序是倒序,先執行MD2的,在執行MD1的,接着執行了視圖函數返回的HttpResponse對象的render方法,返回了一個新的HttpResponse對象,接着執行中間件的process_response方法。

    四 中間件執行流程

      上一部分,咱們瞭解了中間件中的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方法。

        img

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

        img

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

        img

        img

    五 中間件版登錄認證

    中間件版的登陸驗證須要依靠session,因此數據庫中要有django_session表。

        urls.py

    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        url(r'^index/$', views.index),
        url(r'^login/$', views.login, name='login'),
    ]

        views.py

    from django.shortcuts import render, HttpResponse, redirect
    
    
    def index(request):
        return HttpResponse('this is index')
    
    
    def home(request):
        return HttpResponse('this is home')
    
    
    def login(request):
        if request.method == "POST":
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
    
            if user == "Q1mi" and pwd == "123456":
                # 設置session
                request.session["user"] = user
                # 獲取跳到登錄頁面以前的URL
                next_url = request.GET.get("next")
                # 若是有,就跳轉回登錄以前的URL
                if next_url:
                    return redirect(next_url)
                # 不然默認跳轉到index頁面
                else:
                    return redirect("/index/")
        return render(request, "login.html")

        login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>登陸頁面</title>
    </head>
    <body>
    <form action="{% url 'login' %}">
        <p>
            <label for="user">用戶名:</label>
            <input type="text" name="user" id="user">
        </p>
        <p>
            <label for="pwd">密 碼:</label>
            <input type="text" name="pwd" id="pwd">
        </p>
        <input type="submit" value="登陸">
    </form>
    </body>
    </html>

        middlewares.py

    class AuthMD(MiddlewareMixin):
        white_list = ['/login/', ]  # 白名單
        balck_list = ['/black/', ]  # 黑名單
    
        def process_request(self, request):
            from django.shortcuts import redirect, HttpResponse
    
            next_url = request.path_info
            print(request.path_info, request.get_full_path())
    
            if next_url in self.white_list or request.session.get("user"):
                return
            elif next_url in self.balck_list:
                return HttpResponse('This is an illegal URL')
            else:
                return redirect("/login/?next={}".format(next_url))

        在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',
        'middlewares.AuthMD',
    ]

    AuthMD中間件註冊後,全部的請求都要走AuthMD的process_request方法。

    訪問的URL在白名單內或者session中有user用戶名,則不作阻攔走正常流程;

    若是URL在黑名單中,則返回This is an illegal URL的字符串;

    正常的URL可是須要登陸後訪問,讓瀏覽器跳轉到登陸頁面。

    注:AuthMD中間件中須要session,因此AuthMD註冊的位置要在session中間的下方。

    附:Django請求流程圖

          img

    六 中間件案例

      應用案例

        一、作IP訪問頻率限制

          某些IP訪問服務器的頻率太高,進行攔截,好比限制每分鐘不能超過20次。

        二、URL訪問過濾

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

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

相關文章
相關標籤/搜索