細說Django的中間件


分析Django的生命周期,我們知道所有的http請求都要經過Django的中間件.

假如現在有一個需求,所有到達服務端的url請求都在系統中記錄一條日志,該怎么做呢?

Django的中間件的簡介

Django的中間件類似於linux中的管道符

Django的中間件實質就是一個類,類之中有Django已經定義好了一些方法.

每個http請求都會執行中間件中的一個或多個方法

進入Django中的請求都會執行process_request方法;
Django返回的信息都會執行process_response方法.;

Django內部的中間件注冊在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',
]

導入from django.middleware.csrf import CsrfViewMiddleware模塊,查看其源碼

class CsrfViewMiddleware(MiddlewareMixin)

可以看到CsrfViewMiddleware是繼承MiddlewareMixin這個中間件的

再查看MiddlewareMixin的源碼

    class MiddlewareMixin(object):
        def __init__(self, get_response=None):
            self.get_response = get_response
            super(MiddlewareMixin, self).__init__()
    
        def __call__(self, request):
            response = None
            if hasattr(self, 'process_request'):
                response = self.process_request(request)
            if not response:
                response = self.get_response(request)
            if hasattr(self, 'process_response'):
                response = self.process_response(request, response)
            return response

可以知道MiddlewareMixin是調用了其內部的__call__方法,__call__方法以反射的方式執行process_requestprocess_response方法.

中間件的應用

新建一個middleware項目,在項目的根目錄中新建一個名為middleware的包,在這個package中,新建一個middleware.py文件.

    from django.utils.deprecation import MiddlewareMixin
    
    class middle_ware1(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware1.process_request")
    
        def process_response(self,request,response):
            print("middle_ware1.process_response")
            return response

settings.py配置文件的第一行注冊這個中間件

MIDDLEWARE = [
    'middleware.middleware.middle_ware1'
    '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',
]

現在如果有http請求到達服務端,先執行中間件middle_ware1process_request方法,再執行后面的中間件,然后到達視圖函數.

定義一個視圖函數index,配置好路由映射表

def index(request):

    print("index page")
    return HttpResponse("index")

啟動程序,在瀏覽器中輸入http://127.0.0.1:8000/index,會在服務端的后台打印:

middle_ware1.process_request
index page
middle_ware1.process_response

前端的頁面為顯示為:

index

通過這個我們知道,所有的請求都會經過middle_ware1這個中間件,先執行process_request方法,再執行視圖函數本身

最后執行process_response方法,Django會把process_response方法的返回值返回給客戶端.

現在再加一個定義一個middle_ware2的中間件

    from django.utils.deprecation import MiddlewareMixin
    
    class middle_ware2(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware2.process_request")
    
        def process_response(self,request,response):
            print("middle_ware2.process_response")
            return response

settings.py配置文件中注冊中間件

	MIDDLEWARE = [
		'middleware.middleware.middle_ware1',
		'middleware.middleware.middle_ware2',
		'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',
	]

重啟程序,再次刷新網頁,服務端打印信息

middle_ware1.process_request
middle_ware2.process_request
index page
middle_ware2.process_response
middle_ware1.process_response

中間件的依次執行為:

先執行middle_ware1的process_request方法,
再執行middle_ware2的類的process_request方法,
最后才執行Django的視圖函數.
返回時,先執行middle_ware2的類中的process_response函數,
然后再執行middle_ware1的類中的process_response函數.

上面的例子中,process_response方法設置了return值.假如process_response沒有return值,會怎么樣呢??

middle_ware1中間件的process_response方法中的return注釋掉,再次刷新網頁,在瀏覽器的網頁上顯示報錯信息,如下:

A server error occurred.  Please contact the administrator.

http請求到達Django后,先經過自定義的中間件middle_ware1middle_ware2,再經過Django內部定義的中間件到達視圖函數

視圖函數執行完成后,一定會返回一個HttpResponse或者render.

通過查看render的源碼可知,render方法本質上也是調用了HttpResponse

    def render(request, template_name, context=None, content_type=None, status=None, using=None):
    
        content = loader.render_to_string(template_name, context, request, using=using)
        return HttpResponse(content, content_type, status)

視圖函數的返回值HttpResponse先經過Django內部定義的中間件,再經過用戶定義的中間件,最后返回給前端網頁.

所以用戶定義的中間件的process_response方法必須設定返回值,否則程序會報錯.

同時,process_response方法中的形參response就是視圖函數的返回值.
那么我們是否可以自己定義這個形參的返回值呢??

用戶發過來的請求信息是固定的,因為所有的請求信息和返回信息都要經過中間件,中間件有可能會修改返回給用戶的信息
,所以有可能會出現用戶收到的返回值與視圖函數的返回值不一樣的情況.

例如,返回給用戶的信息包含響應頭和響應體,但是開發者在視圖函數中沒有設置響應頭,所以Django會在返回的信息中自動加上響應頭.

前面,process_response方法設置了返回值,process_request中沒有設置返回值,如果為process_response設置一個返回值,結果會怎么樣呢??

為中間件middle_ware1process_request方法設置返回值

return HttpResponse("request message")

刷新瀏覽器,服務端打印信息:

middle_ware1.process_request
middle_ware1.process_response

客戶端瀏覽器顯示信息為:

request message

先執行process_request方法,因為process_request方法有返回值,程序不會再執行后面的中間件,而是直接執行process_response方法,然后返回信息給用戶.

在實際應用中,process_request方法不能直接設定返回值.如果必須在設定返回值,必須在返回值前加入條件判斷語句.

常用在設定網站的黑名單的時候可以為process_request方法設置返回值.

基於中間件實現的簡單用戶登錄驗證

比如,在一個博客系統中,所有的后台管理頁面都必須有用戶登錄后才能打開,可以基於中間件來實現用戶登錄驗證

定義中間件

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse,redirect
    
    class middle_ware1(MiddlewareMixin):
    
        def process_request(self,request):
            if request.path_info == "/login/":
                return None
    
            if not request.session.get("user_info"):
                return redirect("/login/")
    
        def process_response(self,request,response):
            print("middle_ware1.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',
		'middleware.middleware.middle_ware1',
	]

用戶請求index網頁,程序執行到process_request,會執行if not語句,然后重定向到login頁面,使用戶輸入用戶名和密碼登錄進入后台管理頁面.

Django中間件常用方法

除了上面已經用過的process_request方法和porcess_response方法,Django的中間件還有以下幾種方法.

process_view方法

定義兩個中間件如下

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse,redirect
    
    class middle_ware1(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware1.process_request")
    
        def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware1.process_view")
    
        def process_response(self,request,response):
            print("middle_ware1.process_response")
            return response
    
    class middle_ware2(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware2.process_request")
    
        def process_view(self,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware2.process_view")
    
        def process_response(self,request,response):
            print("middle_ware2.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',
    'middleware.middleware.middle_ware1',
    'middleware.middleware.middle_ware2',
]

視圖函數

    def index(request):
    
        print("index page")
        return HttpResponse("index")

在瀏覽器中輸入http://127.0.0.1:8000/index頁面,在服務端打印信息

middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middle_ware2.process_response
middle_ware1.process_response

在前端頁面顯示信息:

index

程序執行流程:

先執行所有中間件中的process_request方法-->進行路由匹配-->執行中間件中的的process_view方法

-->執行對應的視圖函數-->執行中間件中的process_response方法

上面的例子里,process_view方法里沒有設置return值.為process_view方法都設定return值,程序又會怎么執行呢??

process_view方法設定返回值

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse,redirect
    
    class middle_ware1(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware1.process_request")
    
        def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
            return HttpResponse("middle_ware1.process_view")
    
        def process_response(self,request,response):
            print("middle_ware1.process_response")
            return response
    
    class middle_ware2(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware2.process_request")
    
        def process_view(self,request,view_func,view_func_args,view_func_kwargs):
            return HttpResponse("middle_ware2.process_view")
    
        def process_response(self,request,response):
            print("middle_ware2.process_response")

刷新瀏覽器,在服務端打印信息如下:

middle_ware1.process_request
middle_ware2.process_request
middle_ware2.process_response
middle_ware1.process_response

在客戶端的瀏覽器頁面顯示信息如下:

iddle_ware1.process_view

可以看到視圖函數index並沒有執行,程序執行兩個中間件的process_request方法后,其次執行process_view方法.

由於process_view方法設置了return值,所以程序中的視圖函數並沒有執行,而是執行了中間件中的process_response方法.

process_exception方法

修改中間件

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse,redirect
    
    class middle_ware1(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware1.process_request")
    
        def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware1.process_view")
    
        def process_exception(self,request,exvception):
            print("middleware1.process_exception")
    
        def process_response(self,request,response):
            print("middle_ware1.process_response")
            return response
    
    class middle_ware2(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware2.process_request")
    
        def process_view(self,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware2.process_view")
    
        def process_exception(self,request,exception):
            print("middleware2.process_exception")
    
        def process_response(self,request,response):
            print("middle_ware2.process_response")
            return response

刷新瀏覽器,服務端打印信息

middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
hello,index page
middle_ware2.process_response
middle_ware1.process_response

根據服務端的打印信息可以看到,process_exception方法沒有執行,為什么呢??

這是因為上面的代碼沒有bug.當代碼運行錯誤,出現報錯信息的時候,process_exception才會執行

那現在就模擬讓程序出現錯誤,觀察process_exception方法的執行情況

修改視圖函數

    def index(request):
    
        print("index page")
        int("aaaa")
        return HttpResponse("index")

刷新瀏覽器,服務端后台打印信息

middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middleware2.process_exception
middleware1.process_exception
Internal Server Error: /index
Traceback (most recent call last):
......
ValueError: invalid literal for int() with base 10: 'aaaa'
middle_ware2.process_response
middle_ware1.process_response

由服務端的報錯信息可知,這次process_exception方法果然執行了.

由此我們知道,程序運行錯誤,中間件中的process_exception方法才會執行,而程序正常運行的時候,這個方法則不會執行

剛才的代碼里,process_exception方法沒有設置返回值,如果為process_exception方法設置返回值,程序的執行流程會是怎么的呢???

修改中間件,為兩個中間件的process_exception方法都設置返回值

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse,redirect
    
    class middle_ware1(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware1.process_request")
    
        def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware1.process_view")
    
        def process_exception(self,request,exvception):
            print("middleware1.process_exception")
            return HttpResponse("bug1")
    
        def process_response(self,request,response):
            print("middle_ware1.process_response")
            return response
    
    class middle_ware2(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware2.process_request")
    
        def process_view(self,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware2.process_view")
    
        def process_exception(self,request,exception):
            print("middleware2.process_exception")
            return HttpResponse("bug2")
    
        def process_response(self,request,response):
            print("middle_ware2.process_response")
            return response

視圖函數

    def index(request):
    
        print("index page")
        int("aaaa")
        return HttpResponse("index")

刷新頁面,服務端打印信息

middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middleware2.process_exception
middle_ware2.process_response
middle_ware1.process_response

而在瀏覽器的頁面則顯信息

bug2

程序的執行流程為:

客戶端發出的http請求到達中間件,執行中間件中的process_request方法,然后再執行路由匹配,然后執行process_view方法,

然后執行相應的視圖函數,最后執行process_response方法,返回信息給客戶端瀏覽器.

如果執行視圖函數時出現運行錯誤,中間件中的process_exception方法捕捉到異常就會執行,后續的process_exception方法就不會再執行了.

process_exception方法執行完畢,最后再執行所有的process_response方法.

process_template_response方法

process_template_response方法默認也不會被執行,

修改中間件

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse,redirect
    
    class middle_ware1(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware1.process_request")
    
        def process_view(selfs,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware1.process_view")
    
        def process_exception(self,request,exvception):
            print("middle_ware1.process_exception")
            return HttpResponse("bug1")
    
        def process_response(self,request,response):
            print("middle_ware1.process_response")
            return response
    
        def process_template_response(self,request,response):
            print("middle_ware1.process_template_response")
            return response
    
    class middle_ware2(MiddlewareMixin):
    
        def process_request(self,request):
            print("middle_ware2.process_request")
    
        def process_view(self,request,view_func,view_func_args,view_func_kwargs):
            print("middle_ware2.process_view")
    
        def process_exception(self,request,exception):
            print("middleware2.process_exception")
            return HttpResponse("bug2")
    
        def process_response(self,request,response):
            print("middle_ware2.process_response")
            return response
    
        def process_template_response(self,request,response):
            print("middle_ware2.process_template_response")
            return response

修改視圖函數,使視圖函數正常執行


    def index(request):
    
        print("index page")
        return HttpResponse("index")

刷新瀏覽器,服務端后台打印信息如下:

middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
index page
middle_ware2.process_response
middle_ware1.process_response

可以看到,程序運行正常,process_template_response方法沒有執行.

事實上,process_template_response方法的執行取決於視圖函數的返回的信息,

視圖函數如果使用render方法返回信息,中間件里的process_template_response方法就會被執行.

修改視圖函數,定義一個類,返回其參數response

    class MyResponse(object):
        def __init__(self,response):
            self.response=response
    
        def render(self):
            return self.response
    
    def index(request):
    
        response=HttpResponse("index page")
        return MyResponse(response)

MyResponse類返回的是自定義的對象,這個對象里邊調用了render方法.

index視圖函數里,先調用了HttpResponse方法返回信息,再使用MyResponse類中的render方法來返回HttpResponse.

執行程序,服務端后台打印信息如下:

middle_ware1.process_request
middle_ware2.process_request
middle_ware1.process_view
middle_ware2.process_view
middle_ware2.process_template_response
middle_ware1.process_template_response
middle_ware2.process_response
middle_ware1.process_response

可以看到,process_template_response方法已經執行.

process_template_response的用處

如果要對返回的HttpResponse數據進行處理,可以把要處理的信息封裝在一個類里
只要類里定義了render方法,process_template_response方法才會執行.

總結

  • 中間件里本質上就是一個類,
  • 對全局的http請求做處理的時候可以使用中間件
  • 中間件中的方法不一定要全部使用,需要哪個用哪個
  • process_request方法不能有return,一定要使用return的時候,要配合條件判斷語句執行
  • process_response方法一定要有return,否則程序會運行錯誤
  • process_view方法不能有return,否則視圖函數不會執行
  • process_exception方法只有在程序出現運行錯誤的時候才會執行
  • process_exception方法設定return時,程序不會再執行后續中間件中的process_exception
  • process_template_response方法只有在視圖函數中使用render方法返回信息的時候才會執行


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM