首页 > 技术文章 > Django学习之七:Django 中间件

ZJiQi 2018-09-04 23:16 原文

Django 中间件

Tips:
更新日志: 2019.01.31 更新django中间件加载源码阅读

博文图片挂了临时解决办法

Django中间件可看作是包裹在django处理机制的外层,Httprequest和Httpresponse都要经中间件处理,从而起到全局钩子的作用,可以达到一些目的:如过滤请求,预处理请求,响应修改等。

我理解,很多基于会话的应用系统,都可以设计中间件环节。如数据库系统。中间件可以起到全局钩子的作用。django的中间件的设计就是一种递归顺序调用,利用httprequest作为递归调用的参数,httpresponse作为递归调用的return返回。

Django提供了内置的一些中间件。思考:request对象中的user属性中的用户对象是怎么来的,就是会话中间件和认证中间件处理session_id,获取用户对象,从而将用户对象放入request对象中,再交与view中进行处理。request对象在中间件开发中起到了载体的作用,非常重要。

中间件框架,设计上就是嵌套调用,初始化一个中间件函数或者中间件对象;中间件函数的初始化通过一个外层的中间件工厂函数;中间件对象的初始化当然是中间件类的_init_()函数喽。这两种的初始化,工厂函数和类_init_()都需要传入一个get_response函数,这个get_response的传入是django引擎会带入的,一般是初始化后的中间件列表的下一个中间件函数,或者对象,相当于就是嵌套调用递归下去,当最后的view函数处理完后再一层一层返回response,再进行response返回的中间件过程处理。原理设计就是嵌套递归返回模型(我自己起的名字,知道想表达的意思就好):

Tips: 如果图片不好理解,可以查看下面对中间件源码的阅读

自定义中间件 - - - 大体两种方式

中间件工厂函数方式

重点

def simple_middleware(get_response):
    # One-time configuration and initialization.

    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
		# 注意这里的代码是view之前还是之后,两种情景的分隔就是下面这个函数调用,这是基于递归调用设计的关键之处。
		
        response = get_response(request)
		
		# 下面的就是view处理过后,可以处理httpresponse对象。
        # Code to be executed for each request/response after
        # the view is called.

        return response

    return middleware  # 这是django初始化时调用中间件工厂函数,返回中间件函数。

基于中间件类方式

要点

  • 导入父类from django.utils.deprecation import MiddlewareMixin
  • 自定义自己的中间件类继承上条导入的父类
  • 定义要中间件处理的django生命周期的环节处理函数:
    • process_request(self,request)
    • process_view(self, request, callback_view, callback_args, callback_kwargss)
    • process_template_response(request, response)
    • process_response(self, request, response)
    • process_exception(self, request, response, except)
  • 激活中间件,在settings中的MIDDLEWARE列表中放置中间件类或者中间件工厂函数。注意放置在列表中的位置,这个很重要,因为中间可能存在依赖关系(request和response就像就中间件之间传递个的信息的载体;如auth中间件就要放在session中间件后面)。列表中就中间件的full python path(python全路径)字符串.
  • 至于中间件程序模块文件,可以放在python path的任何地方,建议和组件相关放一起。
# 基于类的方式一
from django.utils.deprecation import MiddlewareMixin

class MyMiddle01(MiddlewareMixin):
	# 1. 定义中间件功能,具体处理整个django请求和响应的生命周期的哪一环节。
	# 2. 主要有request请求到达环节;路由Urlconf后View处理前的view预处理环节;View处理过程中抛出异常的对该异常响应的处理环节(异常情况);正常情况下view返回的response环节;还有一个模版环节(不常用);
	def process_request(self, request):
		pass
	
	def process_view(self, request, callback_view, callback_args, callback_kwargs):
		callback_view(request, *callback_args, *callback_kwargs)
		pass

	def process_template_response(request, response):
		pass

	def process_response(self, request, response):
		return response

	def process_exception(request, except):
		pass
	
	
# 基于类的方式二

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
	
	def process_view():
		pass

	def process_exception():
		pass

	def process_template_response():
		pass	

将中间件移除

  1. 方式一:将中间件从配置中移除。
  2. 方式二:中间件初始化是抛出MiddlewareNotUsed异常即可,在初始化中间件函数或者对象时。

实例

权限鉴别中间件

from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse
import re


class RbacPermissionMiddleware(MiddlewareMixin):
    """
    请求访问权限中间件
    """

    def process_request(self, request):
        """
        权限check
        :param request:
        :return:
        """
        req_path = request.path_info
        print('用户请求uri', req_path)
        # 定义白名单url
        white_list = ['/login/', '/register/']
        for url in white_list:
            url = '^%s$' % url
            if re.match(url, req_path):
                return None  # 如果请求权限是白名单中,那么中间件返回一个None,就会进行下一个中间件处理流。

        # 从session中获取权限url列表
        p_list = request.session.get('permissions')
        if not p_list:
            return HttpResponse('未获取用户权限信息,请登录!')

        p_flag = False
        for p_url in p_list:
            p_url = '^%s$' % p_url
            if re.match(p_url, req_path):
                p_flag = True
                break
        if not p_flag:
            return HttpResponse('没有访问权限!')

中间件加载源码阅读

前面也提到了,中间件实际就是一个callable的对象或者函数。中间件的初始化时通过工厂函数或者工厂类产生的。django的多个中间件通过有序的责任链模式设计,通过在settings中配置中间件工厂类的列表顺序。中间件加载或者说实例化时,需要有下一个调用的作为callback参数出入实例化中间件。对于下一个就可以从列表的获取到下一个。而最终就是具体的view视图处理了。而且再实例化每一个中间件的过程中,还从中间件中获取到对视图异常和视图处理前及渲染后的处理三个子流程。这些子流程的执行放在一个叫做BaseHandler._get_response(request)函数中,这个函数是对最终视图view执行的代理者,所以中间件只需要关注_get_response这个代理者,这个代理者再处理进入对应的上个子流程。下面开始一步一步分析源码:从wsgi.py开始

  1. wsgi.py--->wsgi.application--->get_wsgi_application()--->WSGIHandler()
  2. 从WSGIHandler() 开始看源码,这个其实就是wsgi协议的入口函数实例了,WSGIHandler.call(self, environ, start_response)
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()  # 在初始化对象时,就会加载我们的中间件,也就是通过中间件工厂函数或者中间件类实例化出我们的中间件函数,并放于内存栈中,并将第一个中间件存与self.__middleware_chain,作为中间件的起点接口。 下面查看load_middleware()的源码

    def __call__(self, environ, start_response):  
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)  # 执行BaseHandler.get_response(),这里算是

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = list(response.items())
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', c.output(header='')))
        start_response(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)
        return response
  1. BaseHanler.load_middleware
    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE.

        Must be called after the environment is fixed (see __call__ in subclasses).
        """
        self._view_middleware = [] # 存放每个中间件视图前处理单元
        self._template_response_middleware = [] # 存放每个中间件视图模板渲染后处理单元
        self._exception_middleware = []  # 存放每个中间件视图发生异常后处理单元

        handler = convert_exception_to_response(self._get_response)  # 包裹视图代理函数_get_response,将视图处理异常进行捕获返回适当的响应。
        for middleware_path in reversed(settings.MIDDLEWARE):  # 反向便利注册的中间件,从最后一个开始实例,因为只有视图处理函数是可以通过代理_get_response呈现出来的。因为每一个中间都依赖下一个中间作为callback参数才能实例化。
            middleware = import_string(middleware_path)
            try:
                mw_instance = middleware(handler)  # 第一次循环,handler就是_get_response(),这里通过middleware工厂函数得到了mw_instance 中间件实例。
            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)  将中间件作为下轮循环的handler。

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._middleware_chain = handler 循环外层后,最后实例化的中间件赋值给与了_middleware_chian
  1. WSGIHandler初始化构造完成
  2. 进入__call__() django request 生命周期, 主要关注__call__中调用的时get_response(),查看get_response()调用的是_middleware_chain
 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)  # 这里就是到了我们的中间件列表的第一个中间件处理函数了。接下的就是各个中间里面的处理流程,我们看一个中间件,其他都一样了。
  1. 在django中,中间件都继承自from django.utils.deprecation import MiddlewareMixin
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'): 执行我们定义个process_request处理流程,如果在这里响应了,那么就不会走后续的中间件处理。如果中间件处理没有出问题,那么process_request什么也别处理。
            response = self.process_request(request)
        response = response or self.get_response(request) 这里就会执行我们的get_response调用下一个中间件了,这里开始就是递归调用,直到拿到响应。
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response) 开始处理响应流程
        return response

可以看出中间件处理流程就是这样。我们还忽略了最后一点就是最后一个中间件到视图调用的流程。
开始我也说过有一个叫做_get_response的代理视图处理的代理人。我们看下这个代理人的代码:

    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'):  # 获取URLResolver对象,这是一个根路由对象,如果想知道django URL路由原理,可以查看我的另一篇博客https://www.cnblogs.com/ZJiQi/p/10339006.html
            urlconf = request.urlconf
            set_urlconf(urlconf)
            resolver = get_resolver(urlconf)
        else:
            resolver = get_resolver()

        resolver_match = resolver.resolve(request.path_info)  # 通过主路由对象路由到resolvermatch对象
        callback, callback_args, callback_kwargs = resolver_match  # 这里的callback就是url路由对应的视图函数,callback_args 和 call_back_kwargs 就是视图参数。
        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):  # 如果响应是一个通过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
  1. 到这里也就大致阅读完了中间件处理的源码,其他细节遇到时可看。

总结

  1. 中间件函数就类似一个view函数,参数获取request,return返回response。只不过需要递归调用下一个中间件函数。(下一个中间件函数,是中间件工厂函数通过闭包传递给中间件函数的)

  2. 自定义中间件有多种形式:工厂函数,中间件类。

  3. 中间件注册到django项目时,有顺序性。

  4. 中间件会影响全局性能,毕竟所有请求都会进出都需要通过中间件。

  5. 额外的request_process()等都是只能创建在基于类的中间件,因为这些都是通过类的反射方式调用的。 或者异常触发,或者urlconf触发。。。

  6. 中间件函数还提供内部额外的改变请求和响应的路由路径。如:
    视图函数正常执行时:

视图函数抛出异常时:

推荐阅读