天天看點

Django中間件

一、中間件的概念

  中間件顧名思義,是介于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預設的

Middleware可以在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',
]
      

  MIDDLEWARE配置項是一個清單,清單中是一個個字元串,這些字元串其實是一個個類,也就是一個個中間件。

  我們之前已經接觸過一個csrf相關的中間件了,之前将這一行注釋,再送出post請求的時候,就不會被forbidden了,後來學會使用csrf_token就不用再注釋這個中間件了。

  手動引入一個中間件:

from django.middleware.security import SecurityMiddleware
      

  檢視中間件源碼:

Django中間件
Django中間件
import re

from django.conf import settings
from django.http import HttpResponsePermanentRedirect
from django.utils.deprecation import MiddlewareMixin


class SecurityMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        self.sts_seconds = settings.SECURE_HSTS_SECONDS
        self.sts_include_subdomains = settings.SECURE_HSTS_INCLUDE_SUBDOMAINS
        self.sts_preload = settings.SECURE_HSTS_PRELOAD
        self.content_type_nosniff = settings.SECURE_CONTENT_TYPE_NOSNIFF
        self.xss_filter = settings.SECURE_BROWSER_XSS_FILTER
        self.redirect = settings.SECURE_SSL_REDIRECT
        self.redirect_host = settings.SECURE_SSL_HOST
        self.redirect_exempt = [re.compile(r) for r in settings.SECURE_REDIRECT_EXEMPT]
        self.get_response = get_response

    def process_request(self, request):
        path = request.path.lstrip("/")
        if (self.redirect and not request.is_secure() and
                not any(pattern.search(path)
                        for pattern in self.redirect_exempt)):
            host = self.redirect_host or request.get_host()
            return HttpResponsePermanentRedirect(
                "https://%s%s" % (host, request.get_full_path())
            )

    def process_response(self, request, response):
        if (self.sts_seconds and request.is_secure() and
                'strict-transport-security' not in response):
            sts_header = "max-age=%s" % self.sts_seconds
            if self.sts_include_subdomains:
                sts_header = sts_header + "; includeSubDomains"
            if self.sts_preload:
                sts_header = sts_header + "; preload"
            response["strict-transport-security"] = sts_header

        if self.content_type_nosniff and 'x-content-type-options' not in response:
            response["x-content-type-options"] = "nosniff"

        if self.xss_filter and 'x-xss-protection' not in response:
            response["x-xss-protection"] = "1; mode=block"

        return response      

SecurityMiddleware源碼

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

二、自定義中間件

  中間件可以定義五個方法,分别是:(主要的是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對象,則直接将該對象傳回給使用者。

1、process_request, process_response

  當使用者發起請求的時候會依次經過所有的的中間件,這個時候的請求是process_request,最後到達views的函數中,views函數處理後,在依次穿過中間件,這個時候是process_response,最後傳回給請求者。

Django中間件

  上述截圖中的中間件都是django中的,我們也可以自己定義一個中間件,自己寫一個類,但是必須繼承MiddlewareMixin。

  自定義中間件需要引入:

from django.utils.deprecation import MiddlewareMixin
      

  

Django中間件

在視圖函數中:

def index(request):
    print("index.....")
    return HttpResponse("Index")      

在自定義中間件my_middlewares.py中:

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

class CustomerMiddleware(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware process_request...")

    def process_response(self, request, response):
        # 必須有傳回值,要一層層往回傳,不加就會報錯
        print("CustomMiddleware process_response")
        return response

class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware2 process_request...")

    def process_response(self, request, response):
        print("CustomMiddleware2 process_response")
        return response      

 通路index頁面,控制台輸出結果:

CustomMiddleware process_request...
CustomMiddleware2 process_request...
index.....
CustomMiddleware2 process_response
CustomMiddleware process_response
      

  注意:1 .在process_request函數中,如果添加了return語句,會發生中斷現象,程式把請求發給中間件,然後依次傳回給請求者。

     2.在process_response函數中,一定要有return語句,因為需要一層層往回傳值給請求者(浏覽器)。

(1)如果是在CustomerMiddleware類中的process_request函數中添加return  HttpResponse("forbidden..."),輸出如下:

網頁顯示:
forbidden...

控制台輸出:
CustomMiddleware process_request...
CustomMiddleware process_response
      

(2)如果是在CustomerMiddleware2類中的process_request函數中添加return  HttpResponse("forbidden..."),輸出如下:

網頁顯示:
forbidden...

控制台輸出
CustomMiddleware process_request...
CustomMiddleware2 process_request...
CustomMiddleware2 process_response
CustomMiddleware process_response      

 流程圖如下:

Django中間件

2、process_view

def process_view(self, request, callback, callback_args, callback_kwargs):...
      

  my_middlewares.py修改如下:

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

class CustomerMiddleware(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware process_request...")

    def process_response(self, request, response):
        # 必須有傳回值,要一層層往回傳,不加就會報錯
        print("CustomMiddleware process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomMiddleware1  process_view")

class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware2 process_request...")

    def process_response(self, request, response):
        print("CustomMiddleware2 process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomMiddleware2  process_view")
      

  執行結果如下所示:

網頁顯示:
index

控制台輸出
CustomMiddleware process_request...
CustomMiddleware2 process_request...
CustomMiddleware1  process_view
CustomMiddleware2  process_view
index.....
CustomMiddleware2 process_response
CustomMiddleware process_response
      

  上述代碼運作過程見下圖:

Django中間件

  當最後一個中間件的process_request到達路由關系映射之後,傳回到中間件1的process_view,然後依次往下,到達views函數,最後通過process_response依次傳回到達使用者。

  意義:在中間件走完時,多走了一步路由控制,而callback參數就是用來找到這次請求對應的視圖函數(callback_args是視圖函數的參數),是以可以提前一步來執行視圖函數,而如果return直接傳回,則可以跳過視圖函數這一步直接傳回。

示例1:用process_view來調用視圖函數

  僅修改CustomMiddleware2的process_view函數,修改如下:

class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware2 process_request...")
        # return HttpResponse("forbidden...")

    def process_response(self, request, response):
        print("CustomMiddleware2 process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("====》", callback(callback_args))
        print("CustomMiddleware2  process_view")
      

  輸出如下所示:

CustomMiddleware process_request...
CustomMiddleware2 process_request...
CustomMiddleware1  process_view
index.....
====》 <HttpResponse status_code=200, "text/html; charset=utf-8">
CustomMiddleware2  process_view
index.....
CustomMiddleware2 process_response
CustomMiddleware process_response      

 示例2:給process_view函數傳回值

class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware2 process_request...")
        # return HttpResponse("forbidden...")

    def process_response(self, request, response):
        print("CustomMiddleware2 process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        # print("====》", callback(callback_args))
        print("CustomMiddleware2  process_view")
        return HttpResponse("123")
      

輸出如下所示:

頁面顯示:
123

控制台輸出:
CustomMiddleware process_request...
CustomMiddleware2 process_request...
CustomMiddleware1  process_view
CustomMiddleware2  process_view
CustomMiddleware2 process_response
CustomMiddleware process_response
      

  注意:process_view如果有傳回值,會越過其他的process_view以及視圖函數,但是所有的process_response都還會執行。

3、process_exception 

def process_exception(self, request, exception):...
      

  示例修改如下:

class CustomerMiddleware(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware1 process_request...")

    def process_response(self, request, response):
        # 必須有傳回值,要一層層往回傳,不加就會報錯
        print("CustomMiddleware1 process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomMiddleware1  process_view")

    def process_exception(self, request, exception):
        print("CustomMiddleware1 process_exception")


class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware2 process_request...")

    def process_response(self, request, response):
        print("CustomMiddleware2 process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomMiddleware2  process_view")

    def process_exception(self, request, exception):
        print("CustomMiddleware2 process_exception")
      
網頁顯示:
Index

控制台輸出:
CustomMiddleware1 process_request...
CustomMiddleware2 process_request...
CustomMiddleware1  process_view
CustomMiddleware2  process_view
index.....
CustomMiddleware2 process_response
CustomMiddleware1 process_response
      

  注意:在代碼正常情況下不會執行,當視圖中報錯時,才會依次執行process_excepsion和process_response。

  當views出現錯誤時流程圖如下所示:

Django中間件

(1)在視圖函數中填寫錯誤代碼

def index(request):
    print("index.....")
    yuan
    return HttpResponse("Index")
      

  輸出如下:

  1)頁面顯示錯誤提示

Django中間件

  2)控制台輸出

CustomMiddleware1 process_request...
CustomMiddleware2 process_request...
CustomMiddleware1  process_view
CustomMiddleware2  process_view
Internal Server Error: /index/
index.....
CustomMiddleware2 process_exception
CustomMiddleware1 process_exception
大段的錯誤提示
CustomMiddleware2 process_response
CustomMiddleware1 process_response
      

(2)在process_exception中抓取異常資訊,傳回到頁面中顯示

  僅對CustomerMiddleware2中的process_exception修改:

class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self, request):
        print("CustomMiddleware2 process_request...")
        # return HttpResponse("forbidden...")

    def process_response(self, request, response):
        print("CustomMiddleware2 process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomMiddleware2  process_view")

    def process_exception(self, request, exception):
        print("CustomMiddleware2 process_exception")
        return HttpResponse(exception)
      
頁面顯示:
name 'yuan' is not defined

控制台輸出:
CustomMiddleware1 process_request...
CustomMiddleware2 process_request...
CustomMiddleware1  process_view
CustomMiddleware2  process_view
index.....
CustomMiddleware2 process_exception
CustomMiddleware2 process_response
CustomMiddleware1 process_response
      

  CustomMiddleware2的process_exception在捕獲到錯誤後,把return值作為響應體直接傳回了,就不再執行後面的exception了(CustomMiddleware1的),再依次傳給process_response即傳回給浏覽器了。

三、中間件的應用

1、做IP通路頻率限制

  某些IP通路伺服器的頻率過高,進行攔截,比如限制每分鐘不能超過20次。

2、URL通路過濾

  如果使用者通路的是login視圖(放過)

  如果通路其他視圖,需要檢測是不是有session認證,已經有了放行,沒有傳回login,這樣就省得在多個視圖函數上寫裝飾器了!

(1)在settings.py中定義白名單:

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

(2)建立自定義的中間件檔案./app01/my_middlewares.py

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

class AutoMiddleware(MiddlewareMixin):

    def process_request(self, request):
        # 拿到白名單
        white_list = settings.WHITE_LIST
        if request.path in white_list:
            # 路徑在白名單中直接通過
            return None    # return None等同于不寫return ,中間件通過

        # 不在白名單中的路徑需要校驗是否登入驗證
        if not request.user.is_authenticated:
            # 未經過校驗跳轉到登入頁面
            return redirect("/login/")
      

(3)在settings中添加自定義中間件

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.AutoMiddleware",
]
      

  運作程式,并通路index頁面,發現跳轉到登入頁面。

  雖然中間件很友善,但不是所有情況下都應該用中間件,稍有不慎就會降低程式效率。往往視圖函數大多數都需要校驗,則使用中間件比較合适,隻有少量需要校驗則還是使用@login_required裝飾器更加合适。

四、中間件源碼試讀

  主要嘗試着讀以下兩個自帶的中間件:

'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',