1.定義
一個用來處理Django的請求和響應的框架級別的鈎子(函數),相對比較輕量級,並且在全局上改變django的輸入與輸出(使用需謹慎,否則影響性能)
直白的說中間件就是幫助我們在視圖函數執行之前和執行之后做一些額外操作
2.用處
用戶登錄
日志記錄
權限管理
請求驗證(post)
一般對所有請求做批量處理的時候用中間件,單獨對某幾個函數處理直接使用裝飾器
3.用法說明
我們使用django一直就在使用中間件,打開django的setting文件,當中的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', ]
列表當中的每個字符串,其實是一個個類,即一個個中間件,它們正常的執行順序都是自上而下的
中間件中,我們可以定義五個方法,分別是:
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)
其中最常用的是process_request和process_response
以上方法的返回值可以是None或一個HttpResponse對象,如果是None,則繼續按照django定義的規則向后繼續執行,如果是HttpResponse對象,則直接將該對象返回給用戶。
3.1 圖例說明
這里還是先來看看django的生命周期圖例
先從上圖了解django執行流程,每一步起了那些關鍵性作用,然后我們再來討論每一個中間件
3.2 process_request與process_response
process_request有一個request參數,這個和視圖函數中的request參數一模一樣
process_response有兩個參數,一個是request,一個是response,request和前面一致,response是視圖函數返回的HttpResponse對象
代碼實例
我在應用下創建了一個middlewares文件,用於存放中間件
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') def process_response(self, request, response): print('我是MD2的process_response') return response
setting.py文件配制添加的中間價位置
訪問一個視圖,終端輸出結果:
總結:
1.process_request方法,是在視圖函數執行之前執行的,當配制多個中間件時,會按照MIDDLEWARE中的注冊順序,也就是列表的索引值,從前到后依次執行的
返回值是None,繼續往后執行,返回值是HttpResponse的對象,執行對應中間件的process_response方法
2.對於視圖函數,是在process_request執行完畢后開始執行的
3.process_response方法,是在視圖函數之后執行的,而且多個中間件中的process_response方法是按照MIDDLEWARE中的注冊順序倒序執行的
3.3 process_view
process_view(self, request, view_func, view_args, view_kwargs)
該方法有四個參數:
request是HttpRequest對象。
view_func是Django即將使用的視圖函數。 (它是實際的函數對象,而不是函數的名稱作為字符串。)
view_args是將傳遞給視圖的位置參數的列表.
view_kwargs是將傳遞給視圖的關鍵字參數的字典。 view_args和view_kwargs都不包含第一個視圖參數(request)。
代碼實例
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render, redirect, HttpResponse 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('我是MD1的process_view') print(view_func, view_func.__name__)
class MD2(MiddlewareMixin): def process_request(self, request): print('我是MD2的process_request') # return HttpResponse('MD2process_request') def process_response(self, request, response): print('我是MD2的process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print('我是MD2的process_view') print(view_func, view_func.__name__)
效果
總結:
1.process_view是在視圖執行前執行的,process_request之后,執行順序按照MIDDLEWARE中的注冊順序從前到后順序執行的
3.4 process_exception
process_exception(self, request, exception)
該方法兩個參數:
一個HttpRequest對象
一個exception是視圖函數異常產生的Exception對象。
該方法只有在視圖函數中出現異常才會執行,如果視圖函數中無異常,process_exception方法不執行。
代碼實例
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render, redirect, HttpResponse 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('我是MD1的process_view') print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception, '這是MD1的process_exception') # return HttpResponse(str(exception)) class MD2(MiddlewareMixin): def process_request(self, request): print('我是MD2的process_request') # return HttpResponse('MD2process_request') def process_response(self, request, response): print('我是MD2的process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print('我是MD2的process_view') print(view_func, view_func.__name__) # return HttpResponse('這是MD2的process_view') def process_exception(self, request, exception): print(exception, '這是MD2的process_exception') return HttpResponse(str(exception)) # 返回一個響應對象
在視圖函數index中拋出一個異常
def index(request): print('app01中的index視圖') raise ValueError("6666") return HttpResponse('這是index頁面')
此時結果:
# 可以從結果看出來,exception接收的就是異常信息,我們的MD1並沒有return HttpResponse(str(exception)) ,但是它卻也拋出了異常信息
總結:
1.只要視圖函數報錯了才執行
2.在視圖函數之后,process_response之前
3.5 process_template_response(不常用)
process_template_response是在視圖函數執行完成后立即執行,但是它有一個前提條件,那就是視圖函數返回的對象有一個render()方法
4.中間件執行流程
請求到達中間件之后,如果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方法開始倒序執行。
中間件的執行順序:
5.中間件實現登錄驗證
以下代碼還具有阻隔功能,即必須先登錄才能訪問主頁,直接從url訪問index會拒絕請求
代碼實例:
urls.py

urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('logout/', views.logout), path('index/', views.index), path('home/', views.home), ]
views.py

from django.shortcuts import render, HttpResponse, redirect from django.contrib import auth import json # Create your views here. def login(request): if request.method == "POST": user = request.POST.get("user") pwd = request.POST.get("pwd") ret = {"status": 0, 'url': ''} if user == "xiao" and pwd == "123": # 設置session request.session["user"] = user ret['status'] = 1 # 跳轉到index頁面 ret['url'] = '/index/' return HttpResponse(json.dumps(ret)) return render(request, "login.html") def logout(request): # 注銷 auth.logout(request) return redirect("/login/") def index(request): return HttpResponse('this is index <a href="/logout/">注銷</a>') def home(request): return HttpResponse('this is home')
應用app01下的middlewares.py

from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render, redirect, HttpResponse class AuthMD(MiddlewareMixin): # 驗證登錄 white_list = ['/login/', ] # 白名單 black_list = ['/black/', ] # 黑名單 ret = {"status": 0, 'url': '', 'msg': ''} # 默認狀態 def process_request(self, request): # 請求之前 request_url = request.path_info # 獲取請求路徑 # get_full_path()表示帶參數的路徑 print(request.path_info, request.get_full_path()) # 黑名單的網址限制訪問 if request_url in self.black_list: self.ret['msg'] = "這是非法請求" self.ret['url'] = "/login/" # 白名單的網址或者登陸用戶不做限制 # 判斷是否在白名單內或者已經有了session(表示已經登錄了) elif request_url in self.white_list or request.session.get("user"): return None else: self.ret['msg'] = "請登錄后,再訪問!" self.ret['url'] = "/login/" # 錯誤頁面提示 return render(request, "jump.html", self.ret)
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', 'app01.middlewares.AuthMD', # 自定義中間件AuthMD ]
jump.html,用來做中間件不通過時,js跳轉

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <link rel="stylesheet" href="http://mishengqiang.com/sweetalert/css/sweetalert.css"> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script src="http://mishengqiang.com/sweetalert/js/sweetalert-dev.js"></script> <div> {#獲取錯誤信息#} <input type="hidden" id="msg" value="{{ msg }}"> <input type="hidden" id="url" value="{{ url }}"> </div> <script> $(function () { var msg = $("#msg").val(); var url = $("#url").val(); console.log(msg); console.log(url); if (msg.length > 0) { //判斷是否有錯誤信息 swal({ title: msg, text: "2秒后自動關閉。", type: 'error', timer: 2000, showConfirmButton: false }, function () { window.location.href = url; //跳轉指定url }); } }) </script> </body> </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> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/signin.css"> </head> <body> <div class="container"> <form class="form-signin"> {% csrf_token %} <h2 class="form-signin-heading">請登錄</h2> <label for="inputUser" class="sr-only">用戶名</label> <input type="text" id="inputUser" class="form-control" placeholder="用戶名" required="" autofocus="" name="user"> <label for="inputPassword" class="sr-only">密碼</label> <input type="password" id="inputPassword" class="form-control" placeholder="密碼" required="" name="pwd"> <div class="checkbox"> <label> <input type="checkbox" value="remember-me"> 記住我 </label> </div> <input class="btn btn-lg btn-primary btn-block" id="login" value="登陸"> </form> </div> <!-- /container --> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> {#必須先引入jquery,再引入cookie#} <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script> {#sweetalert插件#} <link rel="stylesheet" href="http://mishengqiang.com/sweetalert/css/sweetalert.css"> <script src="http://mishengqiang.com/sweetalert/js/sweetalert-dev.js"></script> <script> //ajax在發送之前,做的header頭。csrfSafeMethod是一個方法名,用來調用的 function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection //這些HTTP方法不需要CSRF保護 // 匹配method的請求模式,js正則匹配用test return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $('#login').click(function () { $.ajax({ url: '/login/', type: 'post', //增加X-CSRFToken的請求頭 headers:{ "X-CSRFToken":$.cookie('csrftoken') }, data: { user: $('[name="user"]').val(), pwd: $('[name="pwd"]').val() }, success: function (data) { data = JSON.parse(data); if (data.status) { swal({ title: '登錄成功', type: 'success', //展示成功的圖片 timer: 500, //延時500毫秒 showConfirmButton: false //關閉確認框 }, function () { window.location.href = data.url; //跳轉后台首頁 }); } else { sweetAlert("登錄失敗!", data.msg, "error"); } } }) }) </script> </body> </html>