Django 處理http請求之中介軟體
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、後續中介軟體被呼叫之前執行(
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之後執行,所以,自定義中介軟體建議都放到最後面。SeeMiddleware orderingfor 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 inMIDDLEWARE, 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(Accessingrequest.POSTinside middleware before the view runs or inprocess_view()will prevent any view running after the middleware from being able tomodify the upload handlers for the request, and should normally be avoided)
CsrfViewMiddleware類可以被看做一個異常,因為它提供csrf_exempt()和csrf_protect()裝飾器,可以顯示控制在哪裡進行CSRF校驗。 (TheCsrfViewMiddlewareclass can be considered an exception, as it provides thecsrf_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