參考:
Django 源碼學習(3)——中間件
Django Middleware官方文檔
Django==2.0.4源碼
一蹋凝、相關(guān)代碼分布
middleware加載:
django.core.handlers.base.BaseHandler的load_middleware()。
運行時:
django.utils.deprecation.MiddlewareMixin铣揉。
django.core.handlers.exception.convert_exception_to_response。
django.core.handlers.base.BaseHandler的get_response()。
二、源碼分析
中間件的操作對象是BaseHandler的_middleware_chain屬性召耘。
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE.
Must be called after the environment is fixed (see __call__ in subclasses).
"""
self._request_middleware = []
self._view_middleware = []
self._template_response_middleware = []
self._response_middleware = []
self._exception_middleware = []
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
try:
mw_instance = middleware(handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
if mw_instance is None:
raise ImproperlyConfigured(
'Middleware factory %s returned None.' % middleware_path
)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.insert(0, mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.append(mw_instance.process_exception)
handler = convert_exception_to_response(mw_instance)
# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._middleware_chain = handler
1、 convert_exception_to_response的作用
convert_exception_to_response(get_response)函數(shù)可用作裝飾器褐隆,不過污它,在方法load_middleware()中是直接調(diào)用。
該函數(shù)接受一個可調(diào)用對象get_response作為入?yún)⑹饕饔檬前烧{(diào)用對象get_response衫贬,當調(diào)用get_response拋出異常時,將異常轉(zhuǎn)換為response歇攻,該response的status_code為4xx或500祥山。
該函數(shù)自動作用于每個middleware(見上述代碼中的兩次調(diào)用),確保每個middleware都不會拋出異常掉伏,從而使得棧中的上一個middleware收到的是一個response對象(為什么是上一個見下文)缝呕,而不是exception對象。
上述內(nèi)容大體上是該函數(shù)的help文檔斧散,如下:
def convert_exception_to_response(get_response):
"""
Wrap the given get_response callable in exception-to-response conversion.
All exceptions will be converted. All known 4xx exceptions (Http404,
PermissionDenied, MultiPartParserError, SuspiciousOperation) will be
converted to the appropriate response, and all other exceptions will be
converted to 500 responses.
This decorator is automatically applied to all middleware to ensure that
no middleware leaks an exception and that the next middleware in the stack
can rely on getting a response instead of an exception.
"""
@wraps(get_response)
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner
2供常、 _middleware_chain的實質(zhì)
接著分析load_middleware()方法,其大體思路是使用convert_exception_to_response將各個middleware包裹起來鸡捐,然后通過中間變量handler來串聯(lián)栈暇,最后賦值給_middleware_chain屬性。
所以該屬性名副其實箍镜。
變量handler初始化使用的是方法_get_response源祈。
3煎源、 排序
在方法load_middleware中,對三類middleware作了處理香缺,分別是:_view_middleware手销,_template_response_middleware, _exception_middleware图张。
其中锋拖,_view_middleware是按照settings.MIDDLEWARE的正序排列,其余二者為逆序祸轮。這些列表里兽埃,都被插入了middleware相對應(yīng)的方法對象。
而_middleware_chain屬性由于是經(jīng)過一次次包裹而來适袜,按照代碼邏輯柄错,也是正序。
另外苦酱,_request_middleware和_response_middleware應(yīng)該是已被廢棄售貌。
那么,這些屬性間有什么關(guān)聯(lián)躏啰?
4、當一個Request進入Django后
當一個Request進入Django后耙册,會調(diào)用BaseHandler的get_response(request)方法给僵。
該方法調(diào)用_middleware_chain屬性:
response = self._middleware_chain(request)
middleware依照順序挨個調(diào)用,如果之前的middleware一直沒有返回response對象详拙,那么就會進入最后被包裹的可調(diào)用對象:BaseHandler的_get_response(request)帝际。
在該方法中,會依次嘗試調(diào)用_view_middleware中的middleware的方法饶辙,_template_response_middleware的處理也類似蹲诀。如果在其中某一步拋出了異常,那么調(diào)用 _exception_middleware中的middleware的方法弃揽。
從上述可見脯爪,如果沒有返回response的話,每個middleware都會被調(diào)用矿微。如果能夠走到BaseHandler的get_response(request)方法痕慢,才會有機會使用_view_middleware、_template_response_middleware涌矢、_exception_middleware掖举。
由于上述三個列表里面是相應(yīng)middleware的方法,所以只有擁有對應(yīng)方法的middleware才會走到娜庇。同時塔次,一旦有返回對象方篮,立即停止繼續(xù)調(diào)用下一個列表中的方法,即“短路”励负。
三藕溅、middleware的定義
按理來說應(yīng)該先明白middleware才好理解上述的內(nèi)容,但是我個人基本上是按照這樣一個過程了解過來的熄守,所以這部分就到最后了蜈垮。
大體上學習官方文檔。
1裕照、什么是middleware
middleware是Django的一套置于Request/Response之間的鉤子框架攒发。
middleware是可調(diào)用的(callable),傳入一個Request晋南,返回一個Response惠猿,和view一樣。
middleware可以通過工廠函數(shù)生成负间,也可以定義為一個callable Class:
class SimpleMiddleware:
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.
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
middleware的__init__(self, get_response)
方法必須有一個入?yún)?em>get_response偶妖,該入?yún)⑹峭瑯邮强烧{(diào)用的。由之前的分析可知政溃,其實就是下一個包裹好的middleware趾访,這樣才能將各個middleware關(guān)聯(lián)起來。具體代碼是load_middleware()方法中的這段:
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
try:
mw_instance = middleware(handler)
middleware的__call__(self, request)
方法必須有一個入?yún)?em>request董虱。在該方法中扼鞋,通過使用self.get_response(request)
來調(diào)用下一個被包裹的middleware,這樣調(diào)用時才會依次調(diào)用愤诱。
2云头、Django提供的middleware Mixin
在django.utils.deprecation中:
class MiddlewareMixin:
def __init__(self, get_response=None):
self.get_response = get_response
super().__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.contrib.auth.middleware.AuthenticationMiddleware就是使用的它(Django自帶的基本都用它),而whitenoise.middleware.WhiteNoiseMiddleware就是自己定義的淫半。
大體的過程是溃槐,先調(diào)用process_request方法嘗試獲取response,如果沒有則通過get_response調(diào)用鏈上的下一個middleware科吭,如果其中一個middleware返回了response昏滴,開始調(diào)用process_response來對response處理,不會去找鏈上的下一個middleware对人,也就是采用了“短路”(short-circuit)原則影涉。
關(guān)于短路,文檔中使用洋蔥作為比喻:
During the request phase, before calling the view, Django applies middleware in the order it’s defined in
MIDDLEWARE
, top-down.
You can think of it like an onion: each middleware class is a “l(fā)ayer” that wraps the view, which is in the core of the onion. If the request passes through all the layers of the onion (each one callsget_response
to pass the request in to the next layer), all the way to the view at the core, the response will then pass through every layer (in reverse order) on the way back out.
If one of the layers decides to short-circuit and return a response without ever calling itsget_response
, none of the layers of the onion inside that layer (including the view) will see the request or the response. The response will only return through the same layers that the request passed in through.
至于view规伐、template蟹倾、exception怎么處理,之前已經(jīng)談到過了。
所以說鲜棠,一個middleware中肌厨,可定義以下幾個方法:process_request,process_response豁陆, process_view柑爸,process_template_response,process_exception盒音”眵ⅲ可少不可多。
3祥诽、middleware的變遷
new:MIDDLEWARE
old:MIDDLEWARE_CLASSES
主要優(yōu)化了兩點:
第一譬圣,對response的處理更加短路化。
第二雄坪,調(diào)整了exception的處理厘熟,處理的地方變了(從上述內(nèi)容可知,convert_exception_to_response可處理维哈,_exception_middleware也處理)绳姨,處理的結(jié)果也變了。
詳情還是看文檔吧阔挠。
四飘庄、最后
如果看不懂,到源碼里調(diào)試一下就懂了购撼。