Django中間件詳解
一.中間件概念
中間件顧名思義,是介於request與response處理之間的一道處理過程,相對比較輕量級,並且在全局上改變django的輸入與輸出。因為改變的是全局,所以需要謹慎實用,用不好會影響到性能。我們從瀏覽器發出一個請求 Request,得到一個響應後的內容 HttpResponse ,這個請求傳遞到 Django的過程如下:
也就是說,每一個請求都是先通過中間件中的 process_request 函數,這個函數返回 None 或者 HttpResponse 對象,如果返回前者,繼續處理其它中間件,如果返回一個 HttpResponse,就處理中止,返回到網頁上。
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 函數。因此中間件中經常使用的函數就是如上的五種。而且中間件隨著Django的版本不同也會不同,這點我們將在下面具體討論。
二.實際應用需求:
比如我們要做一個 攔截器,發現有惡意訪問網站的人,就攔截他!假如我們通過一種技術,比如統計一分鐘訪問頁面的次數,太多就把他的 IP 加入到黑名單 BLOCKED_IPS(這部分沒有提供代碼,主要講中間件部分):
這裏的代碼的功能就是 獲取當前訪問者的 IP (request.META[‘REMOTE_ADDR‘]),如果這個 IP 在黑名單中就攔截,如果不在就返回 None (函數中沒有返回值其實就是默認為 None),把這個中間件的 Python 路徑寫到settings.py中即可生效,這裏只是舉一個簡單的例子,後面會詳細舉例:
class BlockedIpMiddleware(object):
def process_request(self, request):
# 這裏使用反射的方式拿到settings.py文件中的BLOCKED_IPS,其是一個列表,如果獲取不到
# 那就返回一個空列表
"""
因此最終實現的效果就是:BLOCKED_IPS = [‘192.168.4.128‘, ‘192.168.4.129‘, ‘192.168.4.130‘]
if ‘192.168.4.129‘ in [‘192.168.4.128‘, ‘192.168.4.129‘, ‘192.168.4.130‘]:
return http.HttpResponseForbidden(‘<h1>Forbidden</h1>‘)
如果獲取不到BLOCKED_IPS,那就返回一個空列表:
if ‘192.168.4.129‘ in []:
這就不滿足條件,所以不執行
"""
if request.META[‘REMOTE_ADDR‘] in getattr(settings, "BLOCKED_IPS", []):
return http.HttpResponseForbidden(‘<h1>Forbidden</h1>‘)
1.1 Django 1.9 和以前的版本的中間件如下:
MIDDLEWARE_CLASSES = ( # 重點關註這裏的名稱
‘django.contrib.sessions.middleware.SessionMiddleware‘,
‘django.middleware.common.CommonMiddleware‘,
‘django.middleware.csrf.CsrfViewMiddleware‘,
‘django.contrib.auth.middleware.AuthenticationMiddleware‘,
‘django.contrib.auth.middleware.SessionAuthenticationMiddleware‘,
‘django.contrib.messages.middleware.MessageMiddleware‘,
‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
‘django.middleware.security.SecurityMiddleware‘,
‘MiddlewareProject.Middleware.my_middleware_dd.BlockedIpMiddleware‘ # 自定義的中間件
)
1.2 Django 1.10 版本 更名為 MIDDLEWARE(單復同形),寫法也有變化,部署的時候要重新修改名字
如果用 Django 1.10版本開發,部署時用 Django 1.9版本或更低版本,要特別小心此處。
MIDDLEWARE = (
‘django.contrib.sessions.middleware.SessionMiddleware‘,
‘django.middleware.common.CommonMiddleware‘,
‘django.middleware.csrf.CsrfViewMiddleware‘,
‘django.contrib.auth.middleware.AuthenticationMiddleware‘,
‘django.contrib.auth.middleware.SessionAuthenticationMiddleware‘,
‘django.contrib.messages.middleware.MessageMiddleware‘,
‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
‘django.middleware.security.SecurityMiddleware‘,
‘MiddlewareProject.Middleware.my_middleware_dd.BlockedIpMiddleware‘
)
Django 會從 MIDDLEWARE_CLASSES 或 MIDDLEWARE 中按照從上到下的順序一個個執行中間件中的 process_request 函數,而其中 process_response 函數則是最前面的最後執行,關於具體的執行順序筆者將在後面做一個詳細的總結。
二,再比如,我們在網站放到服務器上正式運行後,DEBUG改為了 False,這樣更安全,但是有時候發生錯誤我們不能看到錯誤詳情,調試不方便,有沒有辦法處理好這兩個事情呢?普通訪問者看到的是友好的報錯信息;管理員看到的是錯誤詳情,以便於修復 BUG;當然可以有,利用中間件就可以做到!代碼如下:
import sys
from django.views.debug import technical_500_response
from django.conf import settings
class UserBasedExceptionMiddleware(object):
def process_exception(self, request, exception):
if request.user.is_superuser or request.META.get(‘REMOTE_ADDR‘) in settings.INTERNAL_IPS:
return technical_500_response(request, *sys.exc_info())
把這個中間件像上面一樣,加到你的 settings.py 中的 MIDDLEWARE_CLASSES 中,可以放到最後,這樣可以看到其它中間件的 process_request的錯誤。
當訪問者為管理員時,就給出錯誤詳情,比如訪問本站的不存在的頁面:http://www.ziqiangxuetang.com/admin/
作為一個普通的訪問者,我們看到的是一個比較友好的普通的提示信息:
三.補充:Django 1.10 接口發生變化,變得更加簡潔
class SimpleMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
# 調用 view 之前的代碼
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
# 調用 view 之後的代碼
return response
Django 1.10.x 也可以用函數來實現中間件,詳見官方文檔:https://docs.djangoproject.com/en/1.10/topics/http/middleware/#writing-your-own-middleware
四.寫出一個兼容Django各版本的中間件
try:
from django.utils.deprecation import MiddlewareMixin # Django 1.10.x
except ImportError:
MiddlewareMixin = object # Django 1.4.x - Django 1.9.x
class SimpleMiddleware(MiddlewareMixin):
def process_request(self, request):
pass
def process_response(request, response):
pass
新版本中 django.utils.deprecation.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
__call__ 方法會先調用 self.process_request(request),接著執行 self.get_response(request) 然後調用 self.process_response(request, response).舊版本(Django 1.4.x-Django 1.9.x) 的話,和原來一樣。
六.實例驗證
在講解中間件時,一個Django項目的起始執行如下:先走WSGI,然後再走中間件,註意一點,自帶的中間件的順序是不能倒置的,因為有可能下一個中間件要依賴於上一個中間件的數據,如果隨意顛倒順序,會報錯,我們通常將自定義的中間件放到最下面,本次我們使用的是Django 1.8版本:
MIDDLEWARE_CLASSES = (
‘django.contrib.sessions.middleware.SessionMiddleware‘,
‘django.middleware.common.CommonMiddleware‘,
‘django.middleware.csrf.CsrfViewMiddleware‘,
‘django.contrib.auth.middleware.AuthenticationMiddleware‘,
‘django.contrib.auth.middleware.SessionAuthenticationMiddleware‘,
‘django.contrib.messages.middleware.MessageMiddleware‘,
‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
‘django.middleware.security.SecurityMiddleware‘,
# 自定義中間件
‘MiddlewareDemo.Middleware.my_middleware_dd.MiddleWare1‘,
‘MiddlewareDemo.Middleware.my_middleware_dd.MiddleWare2‘
)
"""
註意在上面,如果自定義的中間件my_middleware.py放在項目根目錄下,那麽在settings中設置的路徑直接就是my_middleware.MiddleWare1
Django能識別的只是根目錄,我們通常會自定義一個目錄,專門用來存放自定義的中間件
"""
接下來,我們一起看看中間件的執行流程,來看一張簡單的圖:
通過上圖,我們可以總結出如下的流程:
request請求經過WSGI後,先進入中間件,依然開始先走process_request函數,然後走路由關系映射後,這裏註意
並沒有直接進入視圖函數,而是從頭開始執行process_view()函數;然後再去執行與urls.py匹配的視圖函數;
如果視圖函數沒有報錯,那就直接挨個反過來從最後一個中間件開始,依次將返回的實例對象(也就是我們在視圖函數中
寫的 return HttpResponse()等等)傳遞給每個中間件的process_response函數;最後再交給客戶端瀏覽器;
如果執行視圖函數出錯,那就反過來從最後一個中間件開始,將錯誤信息傳遞給每個中間件的process_exception()函數,走完所有後,然後最終再走procss_response後,最終再交給客戶端瀏覽器
註意:視圖函數的錯誤是由process_exception()函數處理的,從最後一個中間件開始,依次逐級提交捕捉到的異常
然後最終交給procss_response()函數,將最終的錯誤信息交給客戶端瀏覽器。
process_view的作用和特殊作用在哪?
走process_request的時候不知道走url中的哪個視圖函數,當我們再回來走process_view的時候,由於此時已經走了
urls.py文件,所以它已經知道該執行哪個視圖函數了我們發現process_view的源碼中它的參數多了個callback這個參數的
值就是具體的我們要執行視圖函數;因此我們可以總結其實在process_view中可以執行下我們的視圖函數。
def process_view(self, request, callback, callback_args, callback_kwargs):
這裏的callback就是視圖函數的名稱,因此如果有特殊需求,既不想進入views.py文件中執行視圖函數,但是又想在中間件層面
執行下視圖函數,可以在process_view中,直接調用視圖函數的名稱即可執行:index()
我們首先來看下面的實際例子:
自定義的中間件如下:
# _*_ coding:utf-8 _*_
try:
from django.utils.deprecation import MiddlewareMixin # Django 1.10.x
except ImportError:
MiddlewareMixin = object
"""
process_request是不需要加return的,我們觀察,如果假如return
會發生什麽
"""
class MiddleWare1(object):
def process_request(self, request):
print("MQ1 request=======>")
def process_response(self, request, response):
print("MQ1 response=======>")
"""
這裏一定要返回response,作為中間件的返回
如果不加瀏覽器會報錯:
MiddleWare1.process_response didn‘t return an HttpResponse object.
It returned None instead.
也就是說process_response一定要返回一個response
這裏的response其實質就是我們在視圖函數中返回給瀏覽器的返回值:
return HttpResponse("OK")
也就是說,視圖函數將HttpResponse("OK")作為一個參數傳遞給中間件的
process_response()函數,然後由中間件一層層地往上面傳遞給其他中間件
最終顯示給客戶端瀏覽器;所以是先打印view is running,然後再打印
MQ1 response
"""
return response
視圖函數如下:
from django.shortcuts import render, HttpResponse
# Create your views here.
def index(request):
print("view index is running")
return HttpResponse("index..")
最終打印結果:
MQ1 request=======>
view index is running
MQ1 response=======>
終端的打印結果:
結果印證了我們上面的執行流程,先走process_request()函數,然後再走視圖函數,最後再走process_response()函數
所以是先打印view is running,然後再打印MQ1 response。
接著wo‘men
Django中間件詳解