Django 中的中間件

Django 中的中間件

中間件

中間件介紹

前戲

以前在判斷登陸的時候使用的是裝飾器的功能,經過給視圖函數加裝飾器來增長判斷是否登陸的功能。但此方法的缺點是必須給每一個須要判斷登陸的視圖函數都加上裝飾器。html

中間件可以實現給全部的請求執行相同的操做。python

什麼是中間件

官方介紹:git

中間件是 Django 請求/響應處理的鉤子框架。它是一個輕量級的、低級的「插件」系統,用於全局改變 Django 的輸入或輸出。每一箇中間件組件負責作一些特定的功能。github

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

激活中間件

若要激活中間件組件,請將其添加到 Django 設置中的 MIDDLEWARE 列表中。django

在 MIDDLEWARE 中,每一箇中間件組件由字符串表示:指向中間件工廠的類或函數名的完整 Python 路徑。瀏覽器

咱們一直都在使用中間件,只是沒有注意到而已,打開 Django 項目的 Settings.py 文件,看到下圖的 MIDDLEWARE 配置項。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',
]

例如以前學到的 csrf 其實就是一箇中間件。app

自定義中間件

中間件能夠定義五個方法,分別是:框架

  • process_request
  • process_response
  • process_view
  • process_exception
  • process_template_response

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

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
    'middlewares.MD2'  # 自定義中間件MD2
]

打印結果:

MD1裏面的 process_request
MD2裏面的 process_request
app01 中的 index視圖
  • 參數:
    • self:類實例化後的自身
    • request: 請求對象
  • 執行順序:
    按照註冊的順序(在 settings.py 裏面設置中 從上到下的順序)
  • 什麼時候執行:
    請求從 wsgi 拿到以後,在執行視圖函數以前執行
  • 返回值:
    • 返回 None:繼續執行後續的中間件的 process_request 方法
    • 返回 response: 不執行後續的中間件的 process_request 方法,Django 將不執行視圖函數,而將響應對象返回給瀏覽器。

process_response

示例:

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


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

輸出:

MD2裏面的 process_request
MD1裏面的 process_request
app01 中的 index視圖
MD1裏面的 process_response
MD2裏面的 process_response
  • 參數:
    • self:類實例化後的自身
    • request: 請求對象
    • response:響應對象
  • 執行順序:
    按照註冊順序的倒序(在 settings.py 裏面設置中 從下到上的順序)
  • 什麼時候執行:
    在執行視圖函數執行以後,請求得到響應以後
  • 返回值:
    • 返回值必須是一個 response 對象:將響應對象返回給瀏覽器。

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


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

輸出:

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
  • 參數:
    • self:類實例化後的自身
    • request: 請求對象
    • view_func: Django 即將使用的視圖函數。 (它是實際的函數對象,而不是函數的名稱做爲字符串。)
    • view_args: 將傳遞給視圖的位置參數的列表.
    • view_kwargs: 將傳遞給視圖的關鍵字參數的字典。 view_args 和 view_kwargs 都不包含第一個視圖參數( request )。
  • 執行順序:
    按照註冊的順序(在 settings.py 裏面設置中 從上到下的順序)
  • 什麼時候執行:
    urls.py 中找到對應關係以後 在執行真正的視圖函數以前
  • 返回值:
    • 返回None: 繼續執行後續的中間件的 process_view 方法
    • 返回response: 不執行後續的中間件的 process_view 方法,Django 將不執行視圖函數,而將響應對象返回給瀏覽器。

process_exception

示例:

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("MD2 中的process_exception")

若是視圖函數中無異常,process_exception 方法不執行。在視圖函數中拋出一個異常:

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

輸出:

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

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

  • 參數:
    • self:類實例化後的自身
    • request:請求對象
    • exception:視圖函數異常產生的 Exception 對象
  • 執行順序:
    按照註冊順序的倒序(在 settings.py 裏面設置中 從下到上的順序)
  • 什麼時候執行:
    視圖函數中拋出異常的時候才執行
  • 返回值:
    • 返回None: 繼續執行後續的中間件的 process_exception 方法
    • 返回response: 不執行後續的中間件的 process_exception 方法,而將響應對象返回給瀏覽器。

process_template_response

示例:

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

視圖函數中:

def index(request):
    print("app01 中的 index視圖")

    def render():
        print("in index/render")
        return HttpResponse("O98K")
    rep = HttpResponse("OK")
    rep.render = render
    return rep

輸出:

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

    • self:類實例化後的自身
    • request:請求對象
    • response:響應對象(由視圖函數或者中間件產生)。
  • 執行順序:
    按照註冊順序的倒序(在 settings.py 裏面設置中 從下到上的順序)

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

  • 什麼時候執行:
    在視圖函數執行完成後當即執行,可是它有一個前提條件,那就是視圖函數返回的對象有一個 render() 方法(或者代表該對象是一個 TemplateResponse 對象或等價方法)。

  • 返回值:

    • 返回None: 繼續執行後續的中間件的 process_template_response 方法
    • 返回response: 不執行後續的中間件的 process_template_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 方法。

 


 

 

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 兩個方法的觸發是有條件的,執行順序也是倒序。總結全部的執行流程以下:

 


 

 

 


 

 

中間件版登陸驗證

中間件版的登陸驗證須要依靠 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請求流程圖

 

 


Django請求流程圖

 

參考:http://www.javashuo.com/article/p-bqlawmrb-s.html

GitHub地址:https://github.com/protea-ban/oldboy/tree/master/s9day74/MD_demo

相關文章
相關標籤/搜索