Django學習筆記(15)——中介軟體

戰爭熱誠發表於2019-06-05

  當Django處理一個Request的過程是首先通過中介軟體,然後再通過預設的URL方式進行的。我們可以在Middleware這個地方把所有Request攔截住,用我們自己的方式完成處理以後直接返回Response,因此瞭解中介軟體的構成是非常有必要的。

1,中介軟體的概念

Django預設的Middleware如下:

  在Django專案中的settings模組中,有一個MIDDLEWARE_CLASSES變數,其中每一個元素就是一箇中介軟體,如下:

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 的原始碼,我們首先匯入security:

from django.middleware.security import SecurityMiddleware

  我們檢視原始碼,會發現:

  同樣,我們再檢視一個:

from django.middleware.common import CommonMiddleware

  我們檢視原始碼,會發現:

再來繼續:

from django.middleware.csrf import CsrfViewMiddleware

  我們檢視原始碼,發現:

  我們是不是發現了一個共同的特點。也就是都有process_request和process_response。但是並不是所有的中介軟體都有process_request,但是所有中介軟體都有process_response。而且我們在寫中介軟體的時候,在process_response一定要注意有返回值!!,process_request沒有!!,如果process_request有return值的時候,就不會執行後面的程式了(所以這裡可以攔截)

from django.middleware.clickjacking import XFrameOptionsMiddleware

  檢視原始碼:

 

  我們從瀏覽器發出一個請求Request,得到一個響應後的內容HttpResponse,也就是說,每一個請求都是先通過中介軟體中的process_request函式,這個函式返回None 或者HttpResponse 物件,如果返回前者,繼續處理其他中介軟體,如果返回前者,繼續處理其他中介軟體,如果返回一個HttpResponse,就處理終止,返回到網頁上。

  中介軟體不用整合任何類(可以繼承object),下面一箇中介軟體大概的樣子:

class CommonMiddleware(object):
    def process_request(self, request):
        return None

    def process_response(self, request, response):
        return response

  還有process_view,process_exception和process_template_response函式。

  中介軟體顧名思義,是介於request 與 response處理之間的一道處理過程,相對比較輕量級,並且在全域性上改變django的輸入與輸出。因為改變的是全域性,所以需要謹慎實用,用不好會影響到效能。

Django中間層的定義

Middleware is a framework of hooks into Django’s request/response processing. 

It’s a light, low-level “plugin” system for globally altering Django’s input or output.

  如果你想修改請求,例如被傳送到view中的HTTPRequest物件。或者你想修改view返回的HTTPResponse物件,這些都可以通過中介軟體來實現。

  可能你想在view執行之前做一些操作,這種情況就可以用middleware來實現。

 

2,自定義中介軟體的方法

  中介軟體一共有五個方法:

process_request(self, request)

process_view(self, request, callback, callback_args, callback_kwargs)

process_template_response()

process_exception(self, request, exception)

process_response(self, request, response)

  

2.1  Request 預處理函式:process_request(self, request)

  這個方法的呼叫時機在Django接收到request之後,但仍未解析URL以確定應當執行的檢視函式。Django向它傳入相應的Request物件,以便在方法中修改。

  如果返回None,Django將繼續處理這個request,執行後續的中介軟體,然後呼叫相應的view。

  如果返回HttpResponse物件,Django將不再執行任何除了process_response以外的其他的中介軟體以及相應的view,Django將立即返回該HttpResponse。

 

2.2  View預處理函式:process_view(self, request, callback, callback_args, callback_kwargs)

  這個方法的呼叫時機在Django執行完request預處理函式並確定待執行的view(即callback引數)之後,但在view函式實際執行之前。

request:HttpRequest 物件。

callback:Django將呼叫的處理request的python函式. 這是實際的函式物件本身,
 而不是字串表述的函式名。

args:將傳入view的位置引數列表,但不包括request引數(它通常是傳入view的第一個引數)。

kwargs:將傳入view的關鍵字引數字典。

  process_view() 應當返回None或 HttpResponse 物件。如果返回 None, Django將繼續處理這個request ,執行後續的中介軟體, 然後呼叫相應的view。

如果返回 HttpResponse 物件,Django 將不再執行任何其它的中介軟體(不論種類)以及相應的view,Django將立即返回。

 

2.3  Template模板渲染函式:process_template_response()

  預設不執行,只有在檢視函式的返回結果物件中有render方法才會執行,並把物件的render方法的返回值返回給使用者(注意不返回檢視函式的return的結果了,而是返回檢視函式return值(物件)中render方法的結果)

 

2.4  Exception後處理函式:process_exception(self, request, exception)

  這個方法只有在request處理過程中出了問題並且view函式丟擲了一個未捕獲的異常才會被呼叫。這個鉤子可以用來傳送錯誤通知,將現場相關資訊輸出到日誌檔案,或者甚至嘗試從錯誤中自動恢復。

 這個函式的引數除了一貫的request物件之外,還包括view函式丟擲的實際的異常物件exception 。

  process_exception() 應當返回None或HttpResponse物件。如果返回None,Django將用框架內建的異常處理機制繼續處理相應request。如果返回HttpResponse物件,Django將使用該response物件,而短路框架內建的異常處理機制。

 

2.5  Response 後處理函式:process_response(self, request, response)

  這個方法的呼叫時機在 Django 執行 view 函式並生成 response 之後。

  該處理器能修改response 的內容;一個常見的用途是內容壓縮,如gzip所請求的HTML頁面。

  這個方法的引數相當直觀:request是request物件,而response則是從view中返回的response物件。

  process_response() 必須返回 HttpResponse 物件. 這個 response 物件可以是傳入函式的那一個原始物件(通常已被修改),也可以是全新生成的。

 

3,  自定義中介軟體方法練習

3.1 自定義中介軟體方法應用1(關於process_request 和 process_response方法)

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

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

(為什麼必須繼承MiddlewareMinxin?  當時是因為我們之前檢視settings裡面的中介軟體的情形,他們也是這樣定義的。既然都是中介軟體,我們就需要按照人家的語法來寫)

需要匯入:

from django.utils.deprecation import MiddlewareMixin

  

 

 在 views 中:

def index(request):

    print("view函式...")
    return HttpResponse("OK")

  

在Mymiddlewares.py 中:

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

class Md1(MiddlewareMixin):

    def process_request(self,request):
        print("Md1請求")

    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返回

  流程圖如下:

 

3.2 自定義中介軟體方法應用2(關於process_view 方法)

  Mymiddlewares.py 修改如下:

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

class Md1(MiddlewareMixin):

    def process_request(self,request):
        print("Md1請求")
        #return HttpResponse("Md1中斷")
    def process_response(self,request,response):
        print("Md1返回")
        return response

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

class Md2(MiddlewareMixin):

    def process_request(self,request):
        print("Md2請求")
        # return HttpResponse("Md2中斷")
    def process_response(self,request,response):
        print("Md2返回")
        return response

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

  細心的話,會發現我們增加了 process_view 函式。

  views.py 檢視函式不用改,還是這樣:

def index(request):

    print("view函式...")
    return HttpResponse("OK")

  我們執行程式,結果如下:

Md1請求
Md2請求
Md1view
Md2view
view函式...
Md2返回
Md1返回

  下面繼續使用流程圖分析上面過程:

  當最後一箇中間的 process_request 到達路由關係對映之後,返回到中介軟體1的 process_view ,然後依次往下,到達 views 函式,最後通過 process_response 依次返回到達使用者。

  那麼這樣做有什麼意義?

  我們可以發現,process_view() 裡面有很多的引數。

我們捋一遍過程。

  首先,執行中介軟體1的process_request,然後執行中介軟體2的 process_request:

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

class Md1(MiddlewareMixin):

    def process_request(self,request):
        print("Md1請求")


class Md2(MiddlewareMixin):

    def process_request(self,request):
        print("Md2請求")

  然後去urls.py 去找到 index() 函式:

urlpatterns = [
    path('index/', views.index),
]

  然後返回中介軟體1的process_view 和中介軟體2的 process_view,將拿到的 views.index 傳到 callback,去執行process_view()。

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

class Md1(MiddlewareMixin):

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

class Md2(MiddlewareMixin):

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

  我們也可以嘗試列印 callback這個檢視函式,可以看到其結果如下:

callback =============>

<function  index at 0x103338bf8>

  而process_view中 callback_args 和 claaback_kwargs 是檢視函式的引數。

  當執行完process_view的時候,就去檢視函式執行其函式,然後執行 process_response。

  注意:process_view函式可以用來呼叫檢視函式,我們呼叫如下:

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

class Md1(MiddlewareMixin):

    def process_request(self,request):
        print("Md1請求")

    def process_response(self,request,response):
        print("Md1返回")
        return response

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

class Md2(MiddlewareMixin):

    def process_request(self,request):
        print("Md2請求")

    def process_response(self,request,response):
        print("Md2返回")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("Md2 view")
        return  HttpResponse("123")

  結果如下:

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

  但是請注意,頁面返回的是  ‘123’, 而不是我們檢視函式裡面的 “OK”(為了怕大家混淆,我這裡將index的檢視函式貼上)。

def index(request):

    return HttpResponse("OK")

  如果要返回 OK 的話,怎麼做呢?我們修改程式碼如下:

class Md1(MiddlewareMixin):

    def process_request(self,request):
        print("Md1請求")
        #return HttpResponse("Md1中斷")
    def process_response(self,request,response):
        print("Md1返回")
        return response

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

        # return HttpResponse("hello")

        response=callback(request,*callback_args,**callback_kwargs)
        return response

  結果如下:

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

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

 

3.3 自定義中介軟體方法應用3(關於process_exception 方法)

   Mymiddlewares.py 修改如下:

class Md1(MiddlewareMixin):

    def process_request(self,request):
        print("Md1請求")

    def process_response(self,request,response):
        print("Md1返回")
        return response

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

        # return HttpResponse("hello")

        # response=callback(request,*callback_args,**callback_kwargs)
        # return response
        print("md1 process_view...")

    def process_exception(self, request, exception):
        print("md1 process_exception...")



class Md2(MiddlewareMixin):

    def process_request(self,request):
        print("Md2請求")

    def process_response(self,request,response):
        print("Md2返回")
        return response

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

    def process_exception(self, request, exception):
        print("md2 process_exception...")

  結果如下:

Md1請求
Md2請求
md1 process_view...
md2 process_view...
view函式...
Md2返回
Md1返回

  當沒有問題(異常)的時候,我們發現,和正常的執行沒有任何區別。

  當views出錯的時候, process_exception才會執行,我們看報錯後的執行流程,如下:

  下面我們來模擬一個錯誤,執行看流程:

我們給views.py 裡面加一個簡單的錯誤,程式碼如下:

def index(request):
    print("index  doing")
    james
    return HttpResponse("index  OK")

  我們執行程式,發現其執行流程如下:

Md1請求
Md2請求
md1 process_view...
md2 process_view...
view函式...
md2 process_exception...
md1 process_exception...



Traceback (most recent call last):
  File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\core\handlers\exception.py", line 34,
in inner
    response = get_response(request)
  File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\core\handlers\base.py", line 126, in _
get_response
    response = self.process_exception_by_middleware(e, request)
  File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\core\handlers\base.py", line 124, in _
get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "E:\backup\pycode\web開發\User_model\users\views.py", line 7, in index
    james
NameError: name 'james' is not defined


Md2返回 
Md1返回

  並且前端呼叫如下(當然這是Django自帶預設的錯誤報錯程式):

  我們也可以自己設定報錯,我們將Mymiddlewares.py 修改如下:

class Md1(MiddlewareMixin):

    def process_request(self,request):
        print("Md1請求")

    def process_response(self,request,response):
        print("Md1返回")
        return response

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

        # return HttpResponse("hello")

        # response=callback(request,*callback_args,**callback_kwargs)
        # return response
        print("md1 process_view...")

    def process_exception(self, request, exception):
        print("md1 process_exception...")



class Md2(MiddlewareMixin):

    def process_request(self,request):
        print("Md2請求")

    def process_response(self,request,response):
        print("Md2返回")
        return response

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

    def process_exception(self, request, exception):
        print("md2 process_exception...")
        return HttpResponse(exception)

  我們再執行程式,會發現前臺是這樣的:

  後臺是這樣的:

Md1請求
Md2請求
md1 process_view...
md2 process_view...
view函式...
md2 process_exception...
Md2返回 
Md1返回

  我們發現在執行中介軟體2的process_exception後,不會去執行中介軟體1 了。因為中介軟體2將錯誤處理完之後,直接去執行response了。

 

4,中介軟體應用場景

  由於中介軟體工作在檢視函式執行前,執行後。所以適合所有的請求/一部分請求做批量處理

1,做IP訪問頻率限制

  放在中介軟體類的列表中,阻止某些IP訪問了。

  因為某些IP訪問伺服器的頻率過高,所以進行攔截,比如限制每分鐘不能超過20次。

2,快取

  客戶端請求來了,中介軟體去快取看看有沒有資料,有直接返回給使用者,沒有再去邏輯層,執行檢視函式。

 

3,URL訪問過濾

  如果使用者訪問的是login檢視

  如果訪問的是其他檢視(需要檢測是不是由session已經有了放行,沒有返回login),這樣就省的在多個檢視函式上寫裝飾器了!

  那麼如何寫呢?如下:

Django通過中介軟體實現登入驗證DEMO

  前提:中介軟體版的登入驗證需要依靠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'),
]

  

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 == 'james' and pwd == '123':
            # 設定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">
    <title>登入頁面</title>
</head>
<body>
    <form action="{% url 'login' %}" method="post">
        {% csrf_token %}
        <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>

  

mymiddlewares.py 的程式碼:

# 自定義中介軟體
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect, HttpResponse

class AuthMD(MiddlewareMixin):
    white_list = ['/login/', ]  # 白名單
    black_list = ['/black/', ]  # 黑名單

    def process_request(self, request):
        next_url = request.path_info
        print(request.path_info, request.get_full_path())
        # 黑名單的網址限制訪問
        if next_url in self.black_list:
            return HttpResponse("This is an illegal URL")
        # 白名單的網址或者登入使用者不做限制
        elif next_url in self.white_list or request.session.get("user"):
            return 
        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',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 注意這個中介軟體是自己加的
    'app01.mymiddleware.AuthMD',
]

  AuthMD 中介軟體註冊後,所有的請求都要走AuthMD的process_request方法。如果URL在黑名單中,則返回This is an illegal  URL的字串;訪問的URL在白名單或者session中有user使用者名稱,則不作阻攔走正常流程;正常的URL但是需要登入後訪問,讓瀏覽器跳轉到登入頁面。

  注意:AuthMD中介軟體中需要session,所以AuthMD註冊的位置要在session中間的下方

 

6,跨站請求偽造

1,簡介

  django為使用者實現防止跨站請求偽造的功能,通過中介軟體

django.middleware.csrf.CsrfViewMiddleware 來完成。而對於django中設定防跨站請求偽造功能有分為全域性和區域性。

全域性:

  中介軟體 django.middleware.csrf.CsrfViewMiddleware

區域性:

  • @csrf_protect,為當前函式強制設定防跨站請求偽造功能,即便settings中沒有設定全域性中介軟體。
  • @csrf_exempt,取消當前函式防跨站請求偽造功能,即便settings中設定了全域性中介軟體。

注:from django.views.decorators.csrf import csrf_exempt,csrf_protect

 

2,方式

  方式1:

$.ajaxSetup({
    data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});

  方式2:

<form>
{% csrf_token %}
</form>

$.ajax({
    data:{
    "csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val();
}})

  方法3:

<script src="{% static 'js/jquery.cookie.js' %}"></script>

$.ajax({
 
    headers:{"X-CSRFToken":$.cookie('csrftoken')},
 
})

  

3,應用

普通表單

veiw中設定返回值:
  return render_to_response('Account/Login.html',data,context_instance=RequestContext(request))  
     或者
     return render(request, 'xxx.html', data)
  
html中設定Token:
  {% csrf_token %}

  

ajax

  對於傳統的form,可以通過表單的方式將token再次傳送到服務端,而對於ajax的話,使用如下方式:

views.py

from django.template.context import RequestContext
# Create your views here.
  
  
def test(request):
  
    if request.method == 'POST':
        print request.POST
        return HttpResponse('ok')
    return  render_to_response('app01/test.html',context_instance=RequestContext(request))

  

text.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    {% csrf_token %}
  
    <input type="button" onclick="Do();"  value="Do it"/>
  
    <script src="/static/plugin/jquery/jquery-1.8.0.js"></script>
    <script src="/static/plugin/jquery/jquery.cookie.js"></script>
    <script type="text/javascript">
        var csrftoken = $.cookie('csrftoken');
  
        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });
        function Do(){
  
            $.ajax({
                url:"/app01/test/",
                data:{id:1},
                type:'POST',
                success:function(data){
                    console.log(data);
                }
            });
  
        }
    </script>
</body>
</html>

  

更多:https://docs.djangoproject.com/en/dev/ref/csrf/#ajax

 https://www.cnblogs.com/huchong/p/7819296.html

https://www.cnblogs.com/yuanchenqi/articles/9036479.html

(自己的學習筆記,不喜勿噴,謝謝)

相關文章