官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局範圍內改變Django的輸入和輸出。每一箇中間件組件都負責作一些特定的功能。html
可是因爲其影響的是全局,因此須要謹慎使用,使用不當會影響性能。python
說的直白一點中間件是幫助咱們在視圖函數執行以前和執行以後均可以作一些額外的操做,它本質上就是一個自定義類,類中定義了幾個方法,Django框架會在請求的特定的時間去執行這些方法。jquery
中間件顧名思義,是介於request與response處理之間的一道處理過程,相對比較輕量級,而且在全局上改變django的輸入與輸出。由於改變的是全局,因此須要謹慎實用,用很差會影響到性能ajax
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對象,這些均可以經過中間件來實現。django
可能你還想在view執行以前作一些操做,這種狀況就能夠用 middleware來實現。瀏覽器
Django默認的中間件:(在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配置項是一個列表(列表是有序的,記住這一點,後面你就知道爲何要強調有序二字),列表中是一個個字符串,這些字符串實際上是一個個類,也就是一個個中間件。,每個中間件都有具體的功能cookie
中間件中主要有幾個方法:session
process_request(self,request) process_view(self, request, callback, callback_args, callback_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
第一步:導入
from` `django.utils.deprecation ``import` `MiddlewareMixin
第二步:自定義中間件
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
第三步:在view中定義一個視圖函數(index)
def index(request): print("view函數...") return HttpResponse("OK")
第四步:在settings.py的MIDDLEWARE裏註冊本身定義的中間件
結果:
Md1請求 Md2請求 view函數... Md2返回 Md1返回
注意:若是當請求到達請求2的時候直接不符合條件返回,即return HttpResponse("Md2中斷"),程序將把請求直接發給中間件2返回,而後依次返回到請求者,結果以下:
返回Md2中斷的頁面,後臺打印以下:
Md1請求 Md2請求 Md2返回 Md1返回
流程圖以下:
由此總結一下:
多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。
process_request有一個參數,就是request,這個request和視圖函數中的request是同樣的(在交給Django後面的路由以前,對這個request對象能夠進行一系列的操做)。
因爲request對象是同樣的,因此咱們能夠對request對象進行一系列的操做,包括request.變量名=變量值,這樣的操做,咱們能夠在後續的視圖函數中經過相同的方式便可獲取到咱們在中間件中設置的值。
它的返回值能夠是None也能夠是HttpResponse對象。返回值是None的話,按正常流程繼續走,交給下一個中間件處理,若是是HttpResponse對象,Django將不執行視圖函數,而將相應對象返回給瀏覽器。
咱們來看看多箇中間件時,Django是如何執行其中的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") pass
在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視圖
把MD1和MD2的位置調換一下,再訪問一個視圖,會發現終端中打印的內容以下:
MD2裏面的 process_request MD1裏面的 process_request app01 中的 index視圖
看結果咱們知道:視圖函數仍是最後執行的,MD2比MD1先執行本身的process_request方法。
在打印一下兩個自定義中間件中process_request方法中的request參數,會發現它們是同一個對象。
由此總結一下:
多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。
定義process_response方法時,必須給方法傳入兩個形參,request和response。request就是上述例子中同樣的對象,response是視圖函數返回的HttpResponse對象(也就是說這是Django後臺處理完以後給出一個的一個具體的視圖)。該方法的返回值(必需要有返回值)也必須是HttpResponse對象。若是不返回response而返回其餘對象,則瀏覽器不會拿到Django後臺給他的視圖,而是個人中間件中返回的對象
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
看結果可知:
process_response方法是在視圖函數以後執行的,而且順序是MD1比MD2先執行。(此時settings.py中 MD2比MD1先註冊)
多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。
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並返回結果。
process_view(self, request, callback, callback_args, callback_kwargs) 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")
結果以下:
Md1請求 Md2請求 Md1view Md2view view函數... Md2返回 Md1返回
下圖進行分析上面的過程:
當最後一箇中間的process_request到達路由關係映射以後,返回到中間件1的process_view,而後依次往下,到達views函數,最後經過process_response依次返回到達用戶。
process_view能夠用來調用視圖函數:
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都還會執行。
process_view方法是在Django路由系統以後,視圖系統以前執行的,執行順序按照MIDDLEWARE中的註冊順序從前到後順序執行的
process_exception(self, request, exception)
該方法兩個參數:
一個HttpRequest對象
一個exception是視圖函數異常產生的Exception對象。
這個方法只有在視圖函數中出現異常了才執行,它返回的值能夠是一個None也能夠是一個HttpResponse對象。若是是HttpResponse對象,Django將調用模板和中間件中的process_response方法,並返回給瀏覽器,不然將默認處理異常。若是返回一個None,則交給下一個中間件的process_exception方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。
process_exception(self, request, exception)
示例修改以下:
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 print("md1 process_view...") def process_exception(self,request,exception): print("md1 process_exception...") 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("md2 process_view...") def process_exception(self,request,exception): print("md1 process_exception...")
結果以下:
Md1請求 Md2請求 md1 process_view... md2 process_view... view函數... Md2返回 Md1返回
流程圖以下:
當views出現錯誤時:
將md2的process_exception修改以下:
def process_exception(self,request,exception): print("md2 process_exception...") return HttpResponse("error")
結果以下:
Md1請求 Md2請求 md1 process_view... md2 process_view... view函數... md2 process_exception... Md2返回 Md1返回
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視圖") def render(): print("in index/render") return HttpResponse("O98K") rep = HttpResponse("OK") rep.render = render return rep
訪問index視圖,終端輸出的結果:
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
從結果看出:
視圖函數執行完以後,當即執行了中間件的process_template_response方法,順序是倒序,先執行MD1的,在執行MD2的,接着執行了視圖函數返回的HttpResponse對象的render方法,返回了一個新的HttpResponse對象,接着執行中間件的process_response方法。
上一部分,咱們瞭解了中間件中的5個方法,它們的參數、返回值以及何時執行,如今總結一下中間件的執行流程。
請求到達中間件以後,先按照正序執行每一個註冊中間件的process_request方法,process_request方法返回的值是None,就依次執行,若是返回的值是HttpResponse對象,再也不執行後面的process_request方法,而是執行當前對應中間件的process_response方法(注意不是掉頭執行全部的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兩個方法的觸發是有條件的,執行順序也是倒序。總結全部的執行流程以下:
某些IP訪問服務器的頻率太高,進行攔截,好比限制每分鐘不能超過20次。
若是用戶訪問的是login視圖(放過)
若是訪問其餘視圖,須要檢測是否是有session認證,已經有了放行,沒有返回login,這樣就免得在多個視圖函數上寫裝飾器了!
做爲延伸擴展內容,能夠嘗試着讀一下如下兩個自帶的中間件:
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
中間件版的登陸驗證須要依靠session,因此數據庫中要有django_session表。
urls.py
from app02 import views as v2 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/',v2.login), url(r'^home/',v2.home), url(r'^index/',v2.index) ]
views.py
from django.shortcuts import render,redirect,HttpResponse from app02 import models # Create your views here. def login(request): error_msg='' if request.method=='POST': username=request.POST.get('username') password=request.POST.get('password') user_obj=models.User.objects.filter(username=username,password=password) if user_obj: #設置session request.session['login']='ok' #獲取用戶想直接訪問的URL url=request.GET.get('next') #若是有,就跳轉到客戶初始想訪問的URL if not url: #沒有則默認跳轉到home頁面 url='/home/' return redirect(url) else: error_msg='username or password error!' return render(request,'login.html',{'error_msg':error_msg}) def home(request): return HttpResponse('<h1>這是home頁面 只有登陸了才能看到</h1>') def index(request): return HttpResponse('<h1>這是index頁面 也只有登陸了才能看到<h1>')
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄頁面</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <form action="" method="post"> {% csrf_token %} <label for="">username:<input type="text" name="username"></label> <label for="">password:<input type="password" name="password"></label> <input type="submit" value="submit"> </form> <h1 style="color: red">{{ error_msg }}</h1> </body> </html>
middlewares.py
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect class Check_Login(MiddlewareMixin): def process_request(self,request): next_url=request.path_info if not next_url.startswith('/login/'): is_login=request.session.get('login','') if not is_login: 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', 'middleware.my_middleware.Check_Login', ]
附:Django請求流程圖
<form action="" method="post"> {% csrf_token %} <p>用戶名:<input type="text" name="name"></p> <p>密碼:<input type="text" name="password"></p> <p><input type="submit"></p> </form>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="/static/jquery-3.3.1.js"></script> <title>Title</title> </head> <body> <form action="" method="post"> {% csrf_token %} <p>用戶名:<input type="text" name="name"></p> <p>密碼:<input type="text" name="password" id="pwd"></p> <p><input type="submit"></p> </form> <button class="btn">點我</button> </body> <script> $(".btn").click(function () { $.ajax({ url: '', type: 'post', data: { 'name': $('[name="name"]').val(), 'password': $("#pwd").val(), 'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val() }, success: function (data) { console.log(data) } }) }) </script> </html>
獲取cookie:document.cookie
是一個字符串,能夠本身用js切割,也能夠用jquery的插件
獲取cookie:$.cookie('csrftoken')
設置cookie:$.cookie('key','value')
放在cookie裏
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="/static/jquery-3.3.1.js"></script> <script src="/static/jquery.cookie.js"></script> <title>Title</title> </head> <body> <form action="" method="post"> {% csrf_token %} <p>用戶名:<input type="text" name="name"></p> <p>密碼:<input type="text" name="password" id="pwd"></p> <p><input type="submit"></p> </form> <button class="btn">點我</button> </body> <script> $(".btn").click(function () { var token=$.cookie('csrftoken') //var token='{{ csrf_token }}' $.ajax({ url: '', headers:{'X-CSRFToken':token}, type: 'post', data: { 'name': $('[name="name"]').val(), 'password': $("#pwd").val(), }, success: function (data) { console.log(data) } }) }) </script> </html>
全站禁用:註釋掉中間件 'django.middleware.csrf.CsrfViewMiddleware',
局部禁用:用裝飾器(在FBV中使用)
from django.views.decorators.csrf import csrf_exempt,csrf_protect # 再也不檢測,局部禁用(前提是全站使用) # @csrf_exempt # 檢測,局部使用(前提是全站禁用) # @csrf_protect def csrf_token(request): if request.method=='POST': print(request.POST) return HttpResponse('ok') return render(request,'csrf_token.html')
在CBV中使用:
# CBV中使用 from django.views import View from django.views.decorators.csrf import csrf_exempt,csrf_protect from django.utils.decorators import method_decorator # CBV的csrf裝飾器,只能加載類上(指定方法爲dispatch)和dispatch方法上(django的bug) # 給get方法使用csrf_token檢測 @method_decorator(csrf_exempt,name='dispatch')class Foo(View): def get(self,request): pass def post(self,request): pass