Django中间件及源码解读

洋葱模型

中间件和视图,如同洋葱一层层包裹着最中心的视图想要见到视图函数或返回给浏览器端很不容易,需要在来路和去路都要经过这些中间件。

get_response非常重要,表示去调用下一层的对象。对象可能是下一级中间件,也可能是洋葱心儿——视图。

在阅读wsgi.py源码中,进入 django.core.handlers.wsgi.WSGIHandler 类,可以看到 __init__ 中加载了中间件,在 __call__ 中调用了get_response。

中间件定义

Django1.10版本开始,中间件帮助文档已经不能很好的体现其技术原理了。在官网切换到1.8版本帮

助,看到下面内容 https://docs.djangoproject.com/en/1.8/topics/http/middleware/

image

再看看目前的版本的文档 https://docs.djangoproject.com/en/3.2/topics/http/middleware/

从文档中可以看出保留process_view、process_exception、process_template_response这些钩子函数。

process_view参考 https://docs.djangoproject.com/en/3.2/topics/http/middleware/#process-view

Text
class SimpleMiddleware: def __init__(self, get_response): self.get_response = get_response # One-time configuration and initialization. def __call__(self, request): #request请求去视图的路上 response = self.get_response(request) #内存函数调用完成返回response的路上 return response

新建包utils,在里面增加一个middlewares.py

Text
from django.http import HttpResponse class MagMiddleware1:    def __init__(self, get_response):        """执行一次"""        print(self.__class__.__name__, "init~~~~")        self.get_response = get_response    def __call__(self, request):        # request请求去视图的路上        print(self.__class__.__name__, "__call__~~~~")        # return HttpResponse(self.__class__.__name__) # 测试点        response = self.get_response(request)        # 内层函数调用完成返回response的路上        print(self.__class__.__name__, "__call__#####")        return response    def process_view(self, request, view_func, view_args, view_kwargs):        """调用视图前被调用,返回值是None或HttpResponse对象"""        print(self.__class__.__name__, "process_view~~~~", view_func.__name__, view_args, view_kwargs)        # return HttpResponse(self.__class__.__name__ + ' process_view') # 测试点 class MagMiddleware2:    def __init__(self, get_response):        """执行一次"""        print(self.__class__.__name__, "init~~~~")        self.get_response = get_response    def __call__(self, request):        # request请求去视图的路上        print(self.__class__.__name__, "__call__~~~~")        # return HttpResponse(self.__class__.__name__) # 测试点        response = self.get_response(request)        # 内层函数调用完成返回response的路上        print(self.__class__.__name__, "__call__#####")        return response    def process_view(self, request, view_func, view_args, view_kwargs):        """调用视图前被调用,返回值是None或HttpResponse对象"""        print(self.__class__.__name__, "process_view~~~~", view_func.__name__, view_args, view_kwargs)        # return HttpResponse(self.__class__.__name__ + ' process_view') # 测试点

定义两个中间件,注册

Text
MIDDLEWARE = [    'django.middleware.security.SecurityMiddleware',    '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',    'utils.middlewares.MagMiddleware1',    'utils.middlewares.MagMiddleware2' ]

原理:

image

有人帮你实例化所有的中间件SimpleMiddleware(get_response) 注入get_response,假设实例为A,A(request)实例被调用,一个请求一个实例的调用。

所有请求都会通过它

在主配置文件中注册他,注意配置顺序Order ,实例化顺序相反。

一切从wsgi.py开始,application(envior,start_response)

application = get_wsgi_application() = WSGIHandler() 返回一个可调用对象(类的实例,它是可调用的,有call方法)

WSGI Server帮你调用application之前,需要有application,也就是说执行了WSGIHandler(),实例化的时候load了所有的中间件 self.load_middleware(),那装载的顺序是倒序的

执行顺序,所有中间件按照配置顺序排好队,之后跟着view

a b c d e f 调用深度比较深

process_request 处理阶段,没有视图函数这个参数,

process_view中有视图函数参数,

请求来了,先从外向内,按照中间件配置顺序,依次向内经过process_request,再经过process_view,最终调用view函数,按照中间件配置的顺序,反向return response

Text
request = self.request_class(environ) 将environ请求报文字典包装成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 response = self._middleware_chain(request)

每一级中间件调用get_response方法,相当于调下一级的call方法

结论:

  • Django中间件使用的洋葱式,但有特殊的地方
  • settings.py中配置着中间件的顺序
  • 中间件初始化一次,但是初始化顺序和配置序相反,如同包粽子,先从芯开始包
  • 新版中间件先在__call__中get_response(request)之前代码(相当于老版本中的process_request)
  • 按照配置顺序先后执行所有中间件的getresponse(request)之前代码
  • 全部执行完解析路径映射得到view_func
  • process_view函数按照配置顺序,依次向后执行
    • return None 继续向后执行
    • return HttpResponse()就不再执行其他函数的preview函数了,此函数返回值作为浏览器端的响应
  • 执行view函数,前提是前面的所有中间件process_view都返回None
  • 逆序执行所有中间件的get_response(request)之后代码
  • 特别注意,如果get_response(request)之前代码中return HttpResponse(),将从当前中间件立即返回给浏览器端,从洋葱中依次反弹。

image

应用

应用场景:如果绝大多数请求或相应都需要拦截,个别例外,采用中间件较为合适。白名单。

中间件有很多用途,适合拦截所有请求和响应。例如浏览器端的ip是否禁用、UserAgent分析、异常相应的统一处理。