zoukankan      html  css  js  c++  java
  • django中的中间件机制和执行顺序

    这片文章将讨论下面内容:

    1.什么是middleware

    2.什么时候使用middleware

    3.我们写middleware必须要记住的东西

    4.写一些middlewares来理解中间件的工作过程和要点

    什么是middleware

    Middleware是修改django request 或者 response对象的钩子,下面是django文档中的一段描述

    Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.

    什么时候使用middleware

    如果你想修改请求,例如被传送到view中的HttpResquest对象。或者你想修改view返回的HttpResponse对象,这些都可以通过中间件来实现。

    可能你还想在view执行之前做一些操作,这种情况就可以用middleware来实现。

    django提供了一些默认的moddleware,例如:

    AuthenticationMiddleware

    大家可能频繁在view使用request.user吧。django想在每个view执行之前把user设置为request的属性,于是就用了一个中间件来实现这个目标。所以django提供了可以修改request对象的中间件部分:AuthenticationMiddleware

    from django.conf import settings
    from django.contrib import auth
    from django.contrib.auth import load_backend
    from django.contrib.auth.backends import RemoteUserBackend
    from django.core.exceptions import ImproperlyConfigured
    from django.utils.deprecation import MiddlewareMixin
    from django.utils.functional import SimpleLazyObject
    
    
    def get_user(request):
        if not hasattr(request, '_cached_user'):
            request._cached_user = auth.get_user(request)
        return request._cached_user
    
    
    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))
    
    
    class RemoteUserMiddleware(MiddlewareMixin):
        """
        Middleware for utilizing Web-server-provided authentication.
        If request.user is not authenticated, then this middleware attempts to
        authenticate the username passed in the ``REMOTE_USER`` request header.
        If authentication is successful, the user is automatically logged in to
        persist the user in the session.
        The header used is configurable and defaults to ``REMOTE_USER``.  Subclass
        this class and change the ``header`` attribute if you need to use a
        different header.
        """
    
        # Name of request header to grab username from.  This will be the key as
        # used in the request.META dictionary, i.e. the normalization of headers to
        # all uppercase and the addition of "HTTP_" prefix apply.
        header = "REMOTE_USER"
        force_logout_if_no_header = True
    
        def process_request(self, request):
            # AuthenticationMiddleware is required so that request.user exists.
            if not hasattr(request, 'user'):
                raise ImproperlyConfigured(
                    "The Django remote user auth middleware requires the"
                    " authentication middleware to be installed.  Edit your"
                    " MIDDLEWARE setting to insert"
                    " 'django.contrib.auth.middleware.AuthenticationMiddleware'"
                    " before the RemoteUserMiddleware class.")
            try:
                username = request.META[self.header]
            except KeyError:
                # If specified header doesn't exist then remove any existing
                # authenticated remote-user, or return (leaving request.user set to
                # AnonymousUser by the AuthenticationMiddleware).
                if self.force_logout_if_no_header and request.user.is_authenticated:
                    self._remove_invalid_user(request)
                return
            # If the user is already authenticated and that user is the user we are
            # getting passed in the headers, then the correct user is already
            # persisted in the session and we don't need to continue.
            if request.user.is_authenticated:
                if request.user.get_username() == self.clean_username(username, request):
                    return
                else:
                    # An authenticated user is associated with the request, but
                    # it does not match the authorized user in the header.
                    self._remove_invalid_user(request)
    
            # We are seeing this user for the first time in this session, attempt
            # to authenticate the user.
            user = auth.authenticate(request, remote_user=username)
            if user:
                # User is valid.  Set request.user and persist user in the session
                # by logging the user in.
                request.user = user
                auth.login(request, user)
    
        def clean_username(self, username, request):
            """
            Allow the backend to clean the username, if the backend defines a
            clean_username method.
            """
            backend_str = request.session[auth.BACKEND_SESSION_KEY]
            backend = auth.load_backend(backend_str)
            try:
                username = backend.clean_username(username)
            except AttributeError:  # Backend has no clean_username method.
                pass
            return username
    
        def _remove_invalid_user(self, request):
            """
            Remove the current authenticated user in the request which is invalid
            but only if the user is authenticated via the RemoteUserBackend.
            """
            try:
                stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, ''))
            except ImportError:
                # backend failed to load
                auth.logout(request)
            else:
                if isinstance(stored_backend, RemoteUserBackend):
                    auth.logout(request)
    
    
    class PersistentRemoteUserMiddleware(RemoteUserMiddleware):
        """
        Middleware for Web-server provided authentication on logon pages.
        Like RemoteUserMiddleware but keeps the user authenticated even if
        the header (``REMOTE_USER``) is not found in the request. Useful
        for setups when the external authentication via ``REMOTE_USER``
        is only expected to happen on some "logon" URL and the rest of
        the application wants to use Django's authentication mechanism.
        """
        force_logout_if_no_header = False
    View Code

    假如你有一个应用,它的用户是不同时区的人。你想让他们在访问任何页面的时候都能显示正确的时区,想让所有的views中都能得到用户自己的timezone信息。这种情况下可以用session来解决,所以你可以像下面添加一个middleware:

    class TimezoneMiddleware(object):
        def process_request(self, request):
            # Assuming user has a OneToOneField to a model called Profile
            # And Profile stores the timezone of the User.
            request.session['timezone'] = request.user.profile.timezone

    TimezoneMiddleware是依赖于request.user的,request.user是通过AuthenticationMiddleware来设置的。所以在settings.MIDDLEWARE_CLASSES配置中,TimezoneMiddleware一定要在AuthenticationMiddleware之后。

    下面的例子可以得到关于中间件顺序的更多体会。

    使用middleware时应该记住的东西

      middlewares 的顺序非常重要

      一个middleware只需要继承object类

      一个middleware可以实现一些方法并且不需要实现所有的方法

      一个middleware可以实现process_request(方法)但是不可以实现process_respose(方法)和process_view方法。这些都很常见,django提供了很多middlewares可以做到。

      一个midd可以实现process_response 方法,但是不需要实现process_request 方法。

    AuthenticationMiddleware只实现了对请求的处理,并没有处理响应。

    GZipMiddleware 只实现了对响应的处理,并没有实现对请求和view的处理

    import re
    
    from django.utils.cache import patch_vary_headers
    from django.utils.deprecation import MiddlewareMixin
    from django.utils.text import compress_sequence, compress_string
    
    re_accepts_gzip = re.compile(r'gzip')
    
    
    class GZipMiddleware(MiddlewareMixin):
        """
        Compress content if the browser allows gzip compression.
        Set the Vary header accordingly, so that caches will base their storage
        on the Accept-Encoding header.
        """
        def process_response(self, request, response):
            # It's not worth attempting to compress really short responses.
            if not response.streaming and len(response.content) < 200:
                return response
    
            # Avoid gzipping if we've already got a content-encoding.
            if response.has_header('Content-Encoding'):
                return response
    
            patch_vary_headers(response, ('Accept-Encoding',))
    
            ae = request.META.get('HTTP_ACCEPT_ENCODING', '')
            if not re_accepts_gzip.search(ae):
                return response
    
            if response.streaming:
                # Delete the `Content-Length` header for streaming content, because
                # we won't know the compressed size until we stream it.
                response.streaming_content = compress_sequence(response.streaming_content)
                del response['Content-Length']
            else:
                # Return the compressed content only if it's actually shorter.
                compressed_content = compress_string(response.content)
                if len(compressed_content) >= len(response.content):
                    return response
                response.content = compressed_content
                response['Content-Length'] = str(len(response.content))
    
            # If there is a strong ETag, make it weak to fulfill the requirements
            # of RFC 7232 section-2.1 while also allowing conditional request
            # matches on ETags.
            etag = response.get('ETag')
            if etag and etag.startswith('"'):
                response['ETag'] = 'W/' + etag
            response['Content-Encoding'] = 'gzip'
    
            return response
    参照文档

    写一些middlewares

    首先确认你有一个django项目,需要一个url和view,并且可以进入这个view。下面我们会对request.user做几个测试,确认权限设置好了,并可以在view中正确打印request.user的信息。

    在任意一个app中创建middleware.py文件。

    我有一个叫做books的app,所以文件的位置是books/middleware.py

    class BookMiddleware(object):
        def process_request(self,request):
            print('Middleware executed')

    MIDDLEWARE_CLASSES中添加这个中间件

    MIDDLEWARE_CLASSES = (
        'books.middleware.BookMiddleware',
        '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',
    )

    对任意的一个url发送请求

    Middleware executed

    修改BookMiddleware.process_request如下

    class BookMiddleware(object):
        def process_request(self, request):
            print "Middleware executed"
            print request.user

    再次访问一个url,将会引起一个错误

    'WSGIRequest' object has no attribute 'user'

    这是因为request对象还没有设置user属性呢。

    现在我们改变下middlewares的顺序,BookMiddleware 放在 AuthenticationMiddleware之后

    MIDDLEWARE_CLASSES = (
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'books.middleware.BookMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    )

    访问一个url,runserver控制台打印如下

    Middleware executed
    <username>

    这说明middlewares处理request的顺序跟settings.MIDDLEWARE_CLASSES中列出的顺序是一致的。

    可以进一步证实,middleware.py 添加另外一个middleware

    class AnotherMiddleware(object):
        def process_request(self, request):
            print "Another middleware executed"

    把他也加到MIDDLEWARE_CLASSES

    MIDDLEWARE_CLASSES = (
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'books.middleware.BookMiddleware',
        'books.middleware.AnotherMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    )

    现在的输出是:

    Middleware executed
    <username>
    Another middleware executed

    在process_request方法中返回HttpResponse,把BookMilldeware改成下面这样:

    class BookMiddleware(object):
        def process_request(self, request):
            print "Middleware executed"
            print request.user
            return HttpResponse("some response")

    尝试下任何一个url,会得到如下输出:

    Middleware executed
    <username>

    你会注意到下面两个事情:

      不管你访问哪个url,自己写的view处理方法都不执行了,只有“some response” 这样一种响应。

      AnotherMiddleware.process_request不再被执行。

    所以如果Middleware的process_request方法中返回了HttpResponse对象,那么它之后的中间件将被略过,view中的处理方法也被略过。

    所以在实际的项目中很少会这么干(不过也有些项目会,比如做代理)

    注释掉 "return HttpResponse("some response")" ,两个middleware才能正常的处理请求。

    使用process_response

    给这两个middleware添加process_response方法

    class AnotherMiddleware(object):
        def process_request(self, request):
            print "Another middleware executed"
    
        def process_response(self, request, response):
            print "AnotherMiddleware process_response executed"
            return response
    
    class BookMiddleware(object):
        def process_request(self, request):
            print "Middleware executed"
            print request.user
            return HttpResponse("some response")
            #self._start = time.time()
    
        def process_response(self, request, response):
            print "BookMiddleware process_response executed"
            return response

    访问一些url,得到如下的输出

    Middleware executed
    <username>
    Another middleware executed
    AnotherMiddleware process_response executed
    BookMiddleware process_response executed

    AnotherMiddleware.process_response() 在BookMiddleware.process_response() 之前执行 而 AnotherMiddleware.process_request() 在BookMiddleware.process_request() 之后执行. 所以process_response()  执行的顺序跟 process_request正好相反. process_response() 执行的顺序是从最后一个中间件执行,到倒数第二个,然后直到第一个中间件.

    process_view

    django按顺序执行中间件process_view()的方法,从上到下。类似process_request方法执行的顺序。

    所以如果任何一个process_view() 返回了HttpResponse对象,那么在它后面process_view将会被省略,不会被执行。

  • 相关阅读:
    第4天--linux内核学习
    make menuconfig出错,需要安装libncurses5-dev找不到文件的终极解决办法(不必更换源,适用于ubuntu 32位平台)
    uboot学习第一天
    与或左移右移操作在ARM寄存器配置中的作用
    第四天网络编程笔记
    socket编程热身程序
    线程的创建pthread_create.c
    json.dumps与json.dump的区别 json.loads与json.load的区别
    解决在Vim中鼠标右键不能粘贴
    Python with语句
  • 原文地址:https://www.cnblogs.com/ALADL/p/9898157.html
Copyright © 2011-2022 走看看