Django基礎九之中間件

一 前戲

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

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

二 中間件介紹

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

  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框架會在請求的特定的時間去執行這些方法。cookie

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

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以後就再也不註釋這個中間件了。

三 自定義中間件

中間件能夠定義五個方法,分別是:(主要的是process_request和process_response)app

  • 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,最後返回給請求者。

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

自定義一箇中間件示例

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

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

    def process_response(self, request, response):#request和response兩個參數必需要有,
        print("MD1裏面的 process_response")
        return response #必須返回response,否則你上層的中間件就沒有拿到httpresponse對象,就會報錯
    
class MD2(MiddlewareMixin):
    def process_request(self, request):
        print("MD2裏面的 process_request")

    def process_response(self, request, response):
        print("MD2裏面的 process_response")
        return response
# 在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
]

process_request

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

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

總結一下:

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

process_response

  它有兩個參數,一個是request,一個是response,request就是上述例子中同樣的對象,response是視圖函數返回的HttpResponse對象。該方法的返回值也必須是HttpResponse對象。多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。

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

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

#看結果可知:
# process_response方法是在視圖函數以後執行的,而且順序是MD2比MD1先執行。(此時settings.py中 MD1比MD2先註冊)
#   多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。

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

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

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

流程圖以下:

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

from django.shortcuts import render, HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin

class SessionAuth(MiddlewareMixin):
   def process_request(self, request):
      print('SessionAuth process_request')
      #設置路徑白名單,只要訪問的是login登錄路徑,就不作這個cookie認證
      white_list = ['/login/']
      if request.path in white_list:
         return None
      else:
         is_login = request.session.get('is_login')
         if is_login == True:
            return None
         else:
            return redirect('login')

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

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並返回結果。

給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視圖函數,看一下輸出結果:

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

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

process_exception

process_exception(self, request, exception)

該方法兩個參數:

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

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

給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")
        return HttpResponse(str(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("MD1 中的process_exception")

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

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

def index(request):
    print("app01 中的 index視圖")
    raise ValueError("視圖函數中拋出一個異常")
    return HttpResponse("ok")

看輸出結果:

MD1裏面的 process_request
MD2裏面的 process_request
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x0000022C09727488> index
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x0000022C09727488> index
app01 中的 index視圖
視圖函數中拋出一個異常
MD1 中的process_exception
MD2裏面的 process_response
MD1裏面的 process_response


注意,這裏並無執行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視圖,終端輸出的結果:

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

從結果看出:

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

四 中間件版登錄認證

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

# urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login, name='login'),
    url(r'^index/', views.index, name='index'),
    url(r'^home/', views.home, name='home'),
    url(r'^logout/', views.logout, name='logout'),
]
# views.py
from django.shortcuts import render, redirect, HttpResponse

def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        username = request.POST.get('username')
        password = request.POST.get('password')
        print(username,password)
        if username == 'anwen' and password == '123':
            # 設置session
            request.session['is_login'] = True
            return redirect('index')
        else:
            return redirect('login')

def index(request):
    return render(request, 'index.html')

def home(request):
    return render(request, 'home.html')

def logout(request):
    request.session.flush()
    return redirect('login')

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="username" id="user">
    </p>
    <p>
        <label for="pwd">密 碼:</label>
        <input type="text" name="password" id="pwd">
    </p>
    <input type="submit" value="登陸">
</form>
</body>
</html>

middlewares.py

# -*- coding: utf-8 -*-
# @Time    : 2019/7/31 15:54
# @Author  : AnWen
from django.shortcuts import render, HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin


class SessionAuth(MiddlewareMixin):
    def process_request(self, request):
        print('SessionAuth process_request')
        white_list = ['/login/']  # 白名單
        if request.path in white_list:
            return None
        else:
            is_login = request.session.get('is_login')
            if is_login == True:
                return None
            else:
                return redirect('login')

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

在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.utils.middleware.SessionAuth',
]

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

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

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

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

五 中間件案例

應用案例
一、作IP訪問頻率限制

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

二、URL訪問過濾

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

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

源碼試讀

做爲延伸擴展內容,有餘力的能夠嘗試着讀一下如下兩個自帶的中間件:

'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
相關文章
相關標籤/搜索