Django的中间件

本文主要说一下Django的中间件。中间件是一个钩子框架,负责在Django的请求和响应处理流程中加上一些行为。啥叫钩子呢?钩子感觉像一个挂载点,挂载到对应的函数上,即当要执行某个函数时,就会触发挂载到该函数的中间件。

其实中间件和之前提到过的信号机制有异曲同工之妙,信号机制需要发送信号来触发订阅者,而中间件则是可以自动触发。

在之前学Flask的时候也有钩子,通过4个装饰器实现:

  1. before_first_request:在处理第一个请求之前运行的函数。
  2. before_request:在处理请求之前运行的函数。
  3. after_request:如果没有未处理的异常抛出,在每次请求之后运行。
  4. 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中钩子,主要分为请求阶段和响应阶段:

  1.  在请求阶段,调用视图前:
  • 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.

--------EOF---------
微信分享/微信扫码阅读