Django的中间件
本文主要说一下Django的中间件。中间件是一个钩子框架,负责在Django的请求和响应处理流程中加上一些行为。啥叫钩子呢?钩子感觉像一个挂载点,挂载到对应的函数上,即当要执行某个函数时,就会触发挂载到该函数的中间件。
其实中间件和之前提到过的信号机制有异曲同工之妙,信号机制需要发送信号来触发订阅者,而中间件则是可以自动触发。
在之前学Flask的时候也有钩子,通过4个装饰器实现:
- before_first_request:在处理第一个请求之前运行的函数。
- before_request:在处理请求之前运行的函数。
- after_request:如果没有未处理的异常抛出,在每次请求之后运行。
- teardown_request:即使有未处理的异常抛出,也在每次请求之后运行。
通过上述装饰器修饰的函数,则可以在请求响应流程中的某一个环节执行。
再说下Django的中间件。
Django中间件的使用:
中间件可以使用Django的内置中间件,也可以自定义中间件。如果想用某一个中间件,就一定要在settings中的MIDDLEWARE_CLASSES中定义,如下:
MIDDLEWARE_CLASSES = [
#middleware, a framework of hooks into Django’s request/response processing
'django.middleware.gzip.GZipMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'apps.blog.middleware.LoadTimeMiddleware',
]
上述中用到很多内置的中间件,比如CsrfViewMiddleware,主要为了添加跨站点请求伪造的保护(CSRF),通过向POST表单添加一个隐藏的表单字段,并检查请求中是否有正确的值。
中间件的添加是有顺序的,这是必须要注意的!!!AuthenticationMiddleware在会话中储存已认证的用户。所以它必须在SessionMiddleware之后运行,具体可看:中间件顺序
除了内置的,还可以自定义中间件,我上面的LoadTimeMIddleWare就是自定义的。
如果你想要定义一个中间件,那你就要搞清楚你想在请求\响应处理流程的哪一个环节使用,是在处理请求前,还是在响应流程结束之后等等。比如在Flask中,直接用before_request等等装饰器就可分别实现在不同的环节执行的函数。
当然,Django和FLask的逻辑稍有不同。对于一个完整的请求响应流程,Django提供了5中钩子,主要分为请求阶段和响应阶段:
- 在请求阶段,调用视图前:
- process_request 在输入URL后,server在调度分配某个View之前,调用;
- process_view 获得对应的view之后,在调用view之前调用:
看下源代码:
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'):
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)
try:
response = None
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
if response:
break
if response is None:
if hasattr(request, 'urlconf'):
# Reset url resolver with a custom URLconf.
urlconf = request.urlconf
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.get_resolver(urlconf)
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)
看这一段代码,注意,你定义的process_request返回值一定要注意。如果你返回的不是None,而是一个普通的HttpResponse之类的,那么Django就不会再处理其他的中间件。此外process_view也同样,如果不是None,不会再处理其他中间件或者已经调度的View。
2.在响应阶段:
- process_exception 如果响应的实例有render()方法,process_template_response()在视图刚好执行完毕之后被调用,这表明了它是一个TemplateResponse对象
- process_template_response 当一个视图抛出异常时,Django会调用process_exception()来处理。
- process_response process_response()在所有响应已经处理完,要返回浏览器之前被调用。
源代码:
if 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)
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__))
注意上面这段源代码,执行完process_response之后,就必须要返回一个HttpResponse,否则就会抛出最后一个,这是响应阶段的最后一步了。
好了,说完上面的,自己就可以动手写了。下面是计算一个界面的加载时间:
import time
from utils.get_real_ip import get_real_ip
from utils.cache import Cache
from django.core.cache import caches
cache = caches['memcache']
class LoadTimeMiddleware(object):
def process_request(self,request):
self.start = time.time()
def process_view(self, request, view_func, view_args, view_kwargs):
online_ips = cache.get('onlines',[])
if online_ips:
online_ips = cache.get_many(online_ips).keys()
self.ip = get_real_ip(request)
cache.set(self.ip, 'online_ip')
if self.ip not in online_ips:
online_ips.append(self.ip)
cache.set('onlines',online_ips)
def process_response(self,request,response):
self.end = time.time()
load_time = str((self.end - self.start))[:5]
response.content = response.content.replace('<!!LOAD_TIMES!!>',load_time)
#response.content = response.content.replace('<!!ONLINE_IPS!!>',str(self.ips))
return response
官网给出了两个准则:
- 中间件的类不能是任何类的子类。
- 中间件可以存在于你Python 路径中的任何位置。 Django所关心的只是被包含在MIDDLEWARE_CLASSES中的配置。
One problem:
when DEBUG = True and ALLOWED_HOSTS does not include domain which is setted in nginx .the middleware I wrote above will raise one exception:AttributeError: 'LoadTimeMiddleware' object has no attribute 'start'
I have found this question ,but don't know why.
微信分享/微信扫码阅读