在閱讀資料的時候,經(jīng)常見到資料上說,django處理請求流程的時候,是先middleware處理,如果沒有返回response,那么才到我們寫的視圖view中去處理(包括函數(shù)視圖和對象視圖【基于django-restframe-work】)
那么django的middleware是在什么時候,如何被加載,middleware又做了些什么處理呢?
首先要明確middleware是一個類,他有一些固定名字的一系列方法(process_系列),從django1.10版本起,middleware是繼承自django/utils/deprecations中的MiddlewareMixin類,這是一個可調(diào)用的對象,其代碼如下:
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
其他中間件類可以繼承這個類,然后自己實現(xiàn)中間件中固定的方法,從而實現(xiàn)自己的中間件。
現(xiàn)在我們從頭開始梳理django處理request的流程,進而窺探中間件的處理過程。
先看WSGIHandler類
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super(WSGIHandler, self).__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
try:
request = self.request_class(environ)
print "request.COOKIES: ", request.COOKIES
print "request.HTTP_AUTHORIZATION: ", request.META.get('HTTP_AUTHORIZATION','No HTTP_AUTHORIZATION')
except UnicodeDecodeError:
logger.warning(
'Bad Request (UnicodeDecodeError)',
exc_info=sys.exc_info(),
extra={
'status_code': 400,
}
)
response = http.HttpResponseBadRequest()
else:
response = self.get_response(request)
response._handler_class = self.__class__
status = '%d %s' % (response.status_code, response.reason_phrase)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
start_response(force_str(status), response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
print "type(response), response: ", type(response), response
# print "response.cookies: ", response.items()[0][1]
# print "response.headers: ", response._headers
return response
這里面我們先重點關(guān)注_init_函數(shù)中的self.load_middleware()和_call_函數(shù)中的response = self.get_response(request)
load_middleware的源碼如下:
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE (or the deprecated
MIDDLEWARE_CLASSES).
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 = []
if settings.MIDDLEWARE is None:
warnings.warn(
"Old-style middleware using settings.MIDDLEWARE_CLASSES is "
"deprecated. Update your middleware and use settings.MIDDLEWARE "
"instead.", RemovedInDjango20Warning
)
handler = convert_exception_to_response(self._legacy_get_response)
for middleware_path in settings.MIDDLEWARE_CLASSES:
mw_class = import_string(middleware_path)
try:
mw_instance = mw_class()
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
if hasattr(mw_instance, 'process_request'):
self._request_middleware.append(mw_instance.process_request)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.append(mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.insert(0, mw_instance.process_template_response)
if hasattr(mw_instance, 'process_response'):
self._response_middleware.insert(0, mw_instance.process_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.insert(0, mw_instance.process_exception)
else:
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 six.text_type(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
self.load_middleware()主要作用就是去settings配置文件讀取設(shè)置的middleware,然后初始化WSGIHandler類中的各個middleware的相關(guān)變量,這些變量主要包括self._request_middleware,self._view_middleware,self._response_middleware等存放中間件方法的列表。
WSGIHandler的_call_函數(shù)中的response = self.get_response(request),這也是django處理request的入口
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
set_urlconf(settings.ROOT_URLCONF)
response = self._middleware_chain(request)
# This block is only needed for legacy MIDDLEWARE_CLASSES; if
# MIDDLEWARE is used, self._response_middleware will be empty.
try:
# Apply response middleware, regardless of the response
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
# Complain if the response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__))
except Exception: # Any exception should be gathered and handled
signals.got_request_exception.send(sender=self.__class__, request=request)
response = self.handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
response._closable_objects.append(request)
# If the exception handler returns a TemplateResponse that has not
# been rendered, force it to be rendered.
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render()
if response.status_code == 404:
logger.warning(
'Not Found: %s', request.path,
extra={'status_code': 404, 'request': request},
)
return response
get_response函數(shù)中重點關(guān)注response = self._middleware_chain(request)這句。self._middleware_chain在WSGIHandler調(diào)用_init_的時候調(diào)用self.load_middleware時完成初始化的。當(dāng)settings中的middleware是用MIDDLEWARE_CLASSES 表示時,_middleware_chain其實就是一個被裝飾的_get_response函數(shù),當(dāng)settings中的middleware是MIDDLEWARE表示時,_middleware_chain是一個middleware對象,這個middleware對象中的get_response方法是前面加載的middleware的一個合集(個人理解表述)。具體可以參見上面self.load_middleware的源碼。
下面看_get_response,也就是真正處理request的函數(shù),看明白了這個函數(shù),也就基本明白了django處理request的流程
def _get_response(self, request):
"""
Resolve and call the view, then apply view, exception, and
template_response middleware. This method is everything that happens
inside the request/response middleware.
"""
response = None
if hasattr(request, 'urlconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver()
resolver_match = resolver.resolve(request.path_info)
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match
# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break
if response is None:
wrapped_callback = self.make_view_atomic(callback)
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e:
response = self.process_exception_by_middleware(e, request)
# Complain if the view returned None (a common error).
if response is None:
if isinstance(callback, types.FunctionType): # FBV
view_name = callback.__name__
else: # CBV
view_name = callback.__class__.__name__ + '.__call__'
raise ValueError(
"The view %s.%s didn't return an HttpResponse object. It "
"returned None instead." % (callback.__module__, view_name)
)
# If the response supports deferred rendering, apply template
# response middleware and then render the response
elif hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
# Complain if the template response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_template_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__)
)
try:
response = response.render()
except Exception as e:
response = self.process_exception_by_middleware(e, request)
return response
在_get_response函數(shù)中,首先解析訪問的url,從而獲得后臺開發(fā)者自己寫的view處理函數(shù),也就是callback, callback_args, callback_kwargs = resolver_match中的callback,真正調(diào)用在wrapped_callback = self.make_view_atomic(callback),從_get_response的執(zhí)行順序我們就可以看出,只有在所有的middleware執(zhí)行完后還沒有獲得response,才會執(zhí)行開發(fā)者所寫的view函數(shù),這也是開頭說的,django處理request流程,現(xiàn)有middleware開始,最后才到view函數(shù)。
在django的1.10版本源碼中,并沒有看到誰去顯示的調(diào)用各個中間件的各種函數(shù),比如process_request,那么middleware中的process_request等一些列函數(shù)誰去調(diào)用呢?其實關(guān)鍵點在_middleware_chain函數(shù)。前面提到,在django的1.10版本以前,各個中間件中的函數(shù)在load_middleware的時候放到固定的函數(shù)列表中,然后在固定的流程去執(zhí)行這些函數(shù),但是從1.10版本起,并沒有地方顯示的調(diào)用,剛剛說了,關(guān)鍵點在于1.10版本以后,_middleware_chain已經(jīng)變成了一個特殊的middleware對象了,這個middleware對象中的get_response函數(shù)在每一次加載新的中間件時被迭代更新,從而包含了前面加載的中間件。所以在最后執(zhí)行middleware_chain的時候就相當(dāng)于調(diào)用了中間件類的_call_方法,這個_call_去遞歸調(diào)用前面加載的中間件的_call_方法,從而調(diào)用每一個中間件的定義的process*系列函數(shù)。這是一個難以理解的地方,好好理解load_middelware函數(shù)中的函數(shù)convert_exception_to_response,就可以明白這個點。
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, assigned=available_attrs(get_response))
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner
當(dāng)難以理解某段代碼的時候,可以寫一個小例子測試實驗一下。
from functools import wraps
def available_attrs(fn):
"""
Return the list of functools-wrappable attributes on a callable.
This is required as a workaround for http://bugs.python.org/issue3445
under Python 2.
"""
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
return tuple(a for a in WRAPPER_ASSIGNMENTS if hasattr(fn, a))
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, assigned=available_attrs(get_response))
def inner():
response = None
try:
response = get_response()
except Exception as exc:
print exc
return response
return inner
def get_response():
print "xxxxx"
class A1(object):
def __init__(self, f):
self.f = f
print "A1 init"
def __call__(self, *args, **kwargs):
self.f()
print "A1 call"
class A2(object):
def __init__(self, f):
self.f = f
print "A2 init"
def __call__(self, *args, **kwargs):
self.f()
print "A2 call"
class A3(object):
def __init__(self, f):
self.f = f
print "A3 init"
def __call__(self, *args, **kwargs):
self.f()
print "A3 call"
f = convert_exception_to_response(get_response)
# print dir(f)
f = convert_exception_to_response(A1(f))
# print dir(f)
# f.f()
f = convert_exception_to_response(A2(f))
# print dir(f)
f.f()
f = convert_exception_to_response(A3(f))
# print type(f)
# print dir(f)
# print type(available_attrs)
# print dir(available_attrs)
f()
輸出結(jié)果為
A1 init
A2 init
xxxxx
A1 call
A3 init
xxxxx
A1 call
A2 call
A3 call
通過小例子,就比較清晰的看到convert_exception_to_response函數(shù)做了什么。
比如django.contrib.auth.middleware.AuthenticationMiddleware中的認證函數(shù)process_request就是在這里被調(diào)用的。
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE%s setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
request.user = SimpleLazyObject(lambda: get_user(request))
網(wǎng)上的資料說middleware繼承MiddlewareMixin是從django的1.10版本開始的,前面的版本是沒有繼承對象的,也就是傳統(tǒng)的中間件(legacy middleware)
總結(jié)下來就是,django 1.10版本以前,所有的middlware的方法都是加入到特定的數(shù)組中的,然后依次調(diào)用數(shù)組的中方法處理request和response。1.10版本起,middleware是一個可調(diào)用對象,process_request,get_response, process_response在直接調(diào)用meddleware對象時通過調(diào)用call方法調(diào)用對應(yīng)的函數(shù)。比如用戶認證的AuthenticationMiddleware,就是初始化request.user。
借用網(wǎng)上的一張圖片:

中間件的應(yīng)用場景
由于中間件工作在 視圖函數(shù)執(zhí)行前、執(zhí)行后(像不像所有視圖函數(shù)的裝飾器?。┻m合所有的請求/一部分請求做批量處理
1、做IP限制
放在中間件類的列表中,阻止某些IP訪問了;
2、URL訪問過濾
如果用戶訪問的是login視圖(放過)
如果訪問其他視圖(需要檢測是不是有session已經(jīng)有了放行,沒有返回login),這樣就省得在 多個視圖函數(shù)上寫裝飾器了!
3、緩存(還記得CDN嗎?)
客戶端請求來了,中間件去緩存看看有沒有數(shù)據(jù),有直接返回給用戶,沒有再去邏輯層 執(zhí)行視圖函數(shù)
參考來源:
https://docs.djangoproject.com/en/2.0/topics/http/middleware/
https://code.ziqiangxuetang.com/django/django-middleware.html
http://www.cnblogs.com/huchong/p/7819296.html
http://daoluan.net/%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/2013/09/13/decode-django-have-look-at-middleware.html