1. 程式人生 > 實用技巧 >Django 處理http請求之中介軟體

Django 處理http請求之中介軟體

Django處理http請求之中介軟體

by:授客 QQ1033553122 歡迎加入全國軟體測試交流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之後執行所以,自定義中介軟體建議都放到最後面。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檢視)將看不到當前requestresponseresponse只會經過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物件。如果返回NoneDjango將繼續處理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 responseresponse中介軟體並返回上述描述的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_nameresponse.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