Django處理http請求之中間件
by:授客 QQ:1033553122 歡迎加入全國軟體測驗交流QQ群:7156436
測驗環境
Win7
Django 1.11
自定義中間件
中間件“工廠”是一個攜帶一個可呼叫get_response引數并回傳一個中間件的的可呼叫物件,中間件則是一個攜帶request引數并回傳一個response的可呼叫物件,正如view視圖函式,
中間件可以寫成類似如下的函式(假設以下代碼位于 my_middleware.py檔案中,專案結構如下):

def simple_middleware(get_response):
print('進入中間件')
def middleware(request):
# 針對每個request,這里的代碼,會在view、后續中間件被呼叫之前執行(Code to be executed for each request before the view (and later middleware) are called.)
response = get_response(request)
# 針對每個request,這里的代碼,會在view、后續中間件被呼叫之后執行(Code to be executed for each request/response after the view is called)
return response
return middleware
或者如下,寫成一個類,該類的實體為一個可呼叫物件
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# 只配置并初始化一次(one-time configuration and initialization.)
def __call__(self, request):
# 針對每個request,這里的代碼,會在view、后續中間件被呼叫之前執行(Code to be executed for each request before the view (and later middleware) are called.)
response = self.get_response(request)
# 針對每個request,這里的代碼,會在view、后續中間件被呼叫之前執行(Code to be executed for each request before the view (and later middleware) are called.)
return response
django提供的get_response可能是實際view視圖(如果當前中間是list中配置的最后一個中間件)、下一個中間件,當前中間件不需要知道它是啥,
中間件可以放在python path中的任何地方
__init__(get_response)
中間件工廠必須接受一個get_response引數,可以為中間件初始化一些全域狀態,但是要注意:
- Django只允許用get_response初始化中間件,所以__init__()定義不能包含其它任何引數的,
- 和__call__()方法不一樣,針對每個request,__call__()都會被呼叫一次,而__init__()僅在web 服務器啟動時被呼叫一次(注意:實踐表明 setting.py中 DEBUG = True時,啟動服務時,__init__()可能被呼叫兩次)
標記不被使用的中間件
在對應中間件的 __init__() 方法中拋出 MiddlewareNotUsed,Django將會在處理中間件時移除對應的中間件,并在DEBUG設定為True的情況下,往django.request logger中寫入一條除錯訊息,
激活中間件
添加目標中間件到settings.py中的MIDDLEWARE list中以激活中間件,注意新增中間件后需要重啟服務器,
MIDDLEWARE中,每個中間件以一個字串表示:指向中間件工廠類、函式的全python路徑,如下:
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',
'website.middleware.my_middleware.simple_middleware',
'website.middleware.my_middleware.SimpleMiddleware',
]
MIDDLEWARE可以配置為空,但是強烈建議至少包含CommonMiddleware
中間件在MIDDLEWARE中的順序很關鍵,因為一個中間件可能會依賴另一個中間件,例如 AuthenticationMiddleware在會話中存盤已授權用戶資訊,所以,它必須在SessionMiddleware之后運行,所以,自定義中間件建議都放到最后面,See Middleware ordering for some common hints about ordering of Django middleware classes,
中間件順序和分層
request階段,view呼叫之前,Django會按順序-中間件在MIDDLEWARE中的定義,從上往下(索引從小到大),把中間件作用于request(During the request phase, before calling the view, Django applies middleware in the order it’s defined in MIDDLEWARE, top-down)
可以把它看成一個洋蔥:每個中間件類都是一層包裹了view視圖(洋蔥的核心)的皮,如果請求通過了洋蔥所有皮(每層都會呼叫get_response以便把request傳遞給下一層),到達核心view,那么將按相反的順序,把response一層一層的往外傳,
如果其中一層短路了,沒有呼叫get_response的情況下,回傳了response,該層所包裹的所有層(包括view視圖)將看不到當前request和response,response只會經過request已經通過的層,
其它中間件鉤子
除了上述描述的基礎的request/response中間件模式,還可以添加以下三種特定的方法給基于類的中間件:
process_view()
process_view(request, view_func, view_args, view_kwargs)
request 為一個 HttpRequest 物件,
view_func為Django即將呼叫的python函式 (實際函式物件,而非表示函式名稱的字串)
view_args 傳遞給view函式的位置引數list串列
view_kwargs 傳遞給view函式的字典引數,不管是view_args 還是 view_kwargs都不包含第一個引數(request).
process_view() 在Django呼叫view之前,__call__()被呼叫之后被呼叫,如下:
__call__() ->process_view() -> view function -> __call__()
函式應該回傳None或者一個HttpResponse物件,如果回傳None,Django將繼續處理request,執行其它中間件的process_view(),最后執行對應的view,如果回傳一個HttpResponse物件,Django將不會呼叫對應的view及后續的process_exception(), process_template_response()等,直接呼叫對應的response中間件作用于該response物件并回傳結果.
注意:
應該避免在view視圖運行之前,在中間件內部訪問 request.POST,因為這將阻止該中間件之后的任何視圖 modify the upload handlers for the request(Accessing request.POST inside middleware before the view runs or in process_view() will prevent any view running after the middleware from being able to modify the upload handlers for the request, and should normally be avoided)
CsrfViewMiddleware類可以被看做一個例外,因為它提供csrf_exempt() 和csrf_protect() 裝飾器,可以顯示控制在哪里進行CSRF校驗, (The CsrfViewMiddleware class can be considered an exception, as it provides the csrf_exempt() andcsrf_protect() decorators which allow views to explicitly control at what point the CSRF validation should occur
process_exception()
process_exception(request, exception)
request 為一個 HttpRequest 物件,
exception 為view視圖函式的一個 Exception 物件,
當view拋出一個例外時,Django才會呼叫process_exception(),函式應該回傳None或者一個HttpResponse物件,如果回傳一個HttpResponse物件,將應用template response和response中間件并回傳上述描述的HttpResponse物件,結果給瀏覽器,否則走默認的例外處理(default exception handling )
相反的,response階段(包括process_exception),按逆序運行中間件,如果例外中間件回傳了一個response,位于該中間件前面的中間件(MIDDLEWARElist 中對應索引比當前中間件的索引小的中間件)的process_exception都不會被呼叫,
process_template_response()
process_template_response(request, response)
request 為一個 HttpRequest 物件,
response 為Django view、中間件回傳的一個TemplateResponse物件
process_template_response() 在view視圖執行完成后才被呼叫,如果response實體有render()方法,它將被視為TemplateResponse ,形如:
from django.template.response import TemplateResponse
def test_page(request):
return TemplateResponse(request, 'website/pages/mytest.html',{})
它必須回傳實作了render方法的response物件,可以通過改變response.template_name和response.context_data更改給定response,或者回傳一個全新的TemplateResponse
無需顯示的渲染response--response將在所有template response中間件呼叫完成后自動被渲染,
response階段(包括process_template_response()),按逆序運行中間件,
Dealing with streaming responses
不同于HttpResponse,StreamingHttpResponse沒有content屬性,因此中間件不能認為所有的回應都有content 屬性,如果想要訪問content,需要測驗流式回應:
if response.streaming:
response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
response.content = alter_content(response.content)
注意:streaming_content被假定為太大而不能存放在記憶體中,回應中間件可以將它包裹在一個生成器中,但是不能消費它,通常如下所示:
Def wrap_streaming_content(content):
for chunk in content:
yield alter_content(chunk)
Exception handling
略
例子1
# -*- coding: utf-8 -*-
__author__ = 'shouke'
# from django.http import HttpResponse
class SimpleMiddleware1:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
print('call __init__ in SimpleMiddleware1')
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
print('call __call_ in SimpleMiddleware1 before the view is called')
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
print('call __call_ in SimpleMiddleware1 after the view is called')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('call process_view in SimpleMiddleware1')
# return HttpResponse('shouke')
def process_template_response(self, request, response):
print('call process_template_response in SimpleMiddleware1')
return response
def process_exception(self, request, exception):
print('call process_exception in SimpleMiddleware1')
class SimpleMiddleware2:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
print('call __init__ in SimpleMiddleware2')
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
print('call __call_ in SimpleMiddleware2 before the view is called')
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
print('call __call_ in SimpleMiddleware2 after the view is called')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('call process_view in SimpleMiddleware2')
def process_template_response(self, request, response):
print('call process_template_response in SimpleMiddleware2')
return response
def process_exception(self, request, exception):
print('call process_exception in SimpleMiddleware2')
# return HttpResponse('shouke')
View函式
def test_page(request):
print('call view function test_page')
# 1/0
return TemplateResponse(request, 'website/pages/mytest.html',{})
中間件配置
MIDDLEWARE = [
……
'website.middleware.my_middleware.SimpleMiddleware1',
'website.middleware.my_middleware.SimpleMiddleware2',]
運行結果

Upgrading pre-Django 1.10-style middleware
From django.utils.deprecation import 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
Django提供了django.utils.deprecation.MiddlewareMixin來簡化中間件類的創建,MiddlewareMixin兼容 MIDDLEWARE 和老版本的 MIDDLEWARE_CLASSES,Django包含的所有中間件類都是兼容彼此的配置的,
如果使用 MIDDLEWARE_CLASSES, 將不會呼叫__call__;直接呼叫 process_request() 和process_response()
大多數情況下,直接從MiddlewareMixin繼承創建中間件就夠了,
使用 MIDDLEWARE 和MIDDLEWARE_CLASSES的區別?
略
例子2
修改中間件代碼如下,其它保持不變
# -*- coding: utf-8 -*-
__author__ = 'shouke'
from django.utils.deprecation import MiddlewareMixin
# from django.http import HttpResponse
class SimpleMiddleware1(MiddlewareMixin):
def process_request(self, request):
print('call process_request in SimpleMiddleware1')
def process_response(self, request, response):
print('call process_response in SimpleMiddleware1')
return response
class SimpleMiddleware2(MiddlewareMixin):
def process_request(self, request):
print('call process_request in SimpleMiddleware2')
def process_response(self, request, response):
print('call process_response in SimpleMiddleware2')
return response
運行結果

說明:
process_request在呼叫view函式視圖之前執行;
Process_response在呼叫view函式視圖之后執行;
參考鏈接
https://docs.djangoproject.com/en/2.1/topics/http/middleware/
https://docs.djangoproject.com/en/2.1/_modules/django/middleware/common/#CommonMiddleware
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/30864.html
標籤:Python
上一篇:單片機停車場系統不好用
