當Django處理一個Request的過程是首先通過中間件,然后再通過默認的URL方式進行的。我們可以在Middleware這個地方把所有Request攔截住,用我們自己的方式完成處理以后直接返回Response,因此了解中間件的構成是非常有必要的。
1,中間件的概念
一個完整的Django流程如下:

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
(自己的學習筆記,不喜勿噴,謝謝)
