忠诚的皇家守卫团——中间件

一、什么是中间件呢?
二、中间件的五员大将
三、中间件的执行流程
 
 
 
一、什么是中间件呢?

枯燥的中间件概念
按照官方的说法,中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局变量中改变Django的输入和输入。每个中间件组件都负责做一些特定的功能。但由于其影响的是全局,因此要谨慎使用。
那到底什么是中间件呢?中间件其实就是帮助我们在Django的运行过程中的某个环节来执行特定的操作,比如在视图函数执行之后response一个特定的内容。它的本质是定义了一个类,类中定义了一些方法,Django框架会在请求的特定的时间去执行这些方法。
 
emmm...看不懂,让我们来从源码中找到答案吧!
1.让我们先来看一下一直在默默付出的Django自带的中间件(setting.py中的MIDDLEWARE配置项中)
# 中间件相关的都配置在这里
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',    # session相关的中间件
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',    # csrf相关的中间件
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
 
2.既然Django自带有中间件,那么我们不妨去观摩一下它们的本质(源码)吧!
# 查看中间件源码的方法:
# 1.先写from A import B
# 2.按住Ctrl,鼠标同时点击B
from django.middleware.security import SecurityMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.middleware.common import CommonMiddleware
from django.middleware.csrf import CsrfViewMiddleware
 
3.以下是它们的大致内容:
# 由于源码太多,我们只截取大致框架,具体我们之后再讨论
# django.middleware.security.SecurityMiddleware 的源码
 
class SecurityMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):    # 初始化
        pass
    def process_request(self, request):    # 处理请求的实例方法
        pass
    def process_response(self, request, response):    #处理响应的实例方法
        pass
# django.contrib.sessions.middleware.SessionMiddleware 的源码
 
class SessionMiddleware(MiddlewareMixin):    # 定义了一个类,继承MiddlewareMixin
    def __init__(self, get_response=None):    # 初始化
        pass
    def process_request(self, request):    # 处理请求的实例方法
        pass
    def process_response(self, request, response):    # 处理响应的实例方法
        pass
# django.middleware.csrf.CsrfViewMiddleware 的源码
 
class CsrfViewMiddleware(MiddlewareMixin):    # 定义了一个类,继承MiddlewareMixin
    def _accept(self, request):    # 私有方法
        pass
    def _reject(self, request, reason):    # 私有方法
        pass
    def _get_token(self, request):    # 私有方法
        pass
    def _set_token(self, request, response):    # 私有方法
        pass
    def process_request(self, request):    # 处理请求的实例方法
        pass
    def process_view(self, request, callback, callback_args, callback_kwargs):    # 处理视图的实例方法
        pass
    def process_response(self, request, response):    # 处理响应的实例方法
        pass
 
4.通过对Django自带的几个中间件源码的大致分析,我们可以发现,其实中间件就是定义了一个类,类中写了几个方法:比如处理响应的、处理视图的、处理请求的..这些东西好像很固定?!好吧,接下来让我们来具体分析一下它的实例方法!
 
 
 
二、中间件的五员大将

 
中间件的五员大将,分别是:(主要的是process_request和process_response)
process_request(self,request)    # 处理请求的
process_view(self, request, view_func, view_args, view_kwargs)    # 处理视图的
process_template_response(self,request,response)    # 处理模板相关的
process_exception(self, request, exception)    # 处理异常的
process_response(self, request, response)    # 处理响应的
而它们的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。
第一个要了解的是两名守门大将(request 和 response)
 
第一步:新建一个与manage.py同级别的py文件
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin    # 导入要继承的MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse    # 导入三件套
 
class test1(MiddlewareMixin):    #定义一个名为test1的类,继承MiddlewareMixin
    def process_request(self, request):    #处理请求的实例方法
        print('-----test1-----request-----')    #打印用来分析test1的request、response与test2的request、response的执行顺序
        return    #返回None,正常执行,如果返回的是HttpResponse,则不执行后面的内容,直接往回响应
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response    #通过分析源码得到(这里需要自己分析)返回response是正常执行,因此,只需要中间件打印,打印之后就正常执行
 
class test2(MiddlewareMixin):    #定义一个名为test2的类,与test1做对比
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
第二步:把新写的中间件添加到Django的配置中
MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'mymiddleware.test1',    # mymiddleware.py文件下的test1类
    'mymiddleware.test2',    # mymiddleware.py文件下的test2类
]
 
第三步:运行server,查看结果并分析
-----test1-----request-----
-----test2-----request-----
-----views-----    #这里是我在views中写了一个函数,这个函数打印-----views-----
-----test2-----response-----
-----test1-----response-----
 
第四步:分析
          a. 首先运行test1类中处理request的实例方法,打印出了-----test1-----request-----
          b. 然后运行test2类中处理request的实例方法,打印出了-----test2-----request-----
          c. 接着运行到视图函数中,打印出了-----views-----
          d. 然后从视图函数中拿到response,开始往回走,由于来的时候先经过test1,在经过test2,所以在回去的时候,要先经过test2,再经过test1,因此打印出了-----test2-----response-----
          e. 走过test2的response之后,走到test1的response,打印出了-----test1-----response-----
          f.顺利走过自定义的中间件所有流程之后,拿着response通过wsgi返回给浏览器
 
第五步:总结
          a. process_request() 只有一个参数request,这个request和视图函数中的request是一样的。它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
          b. 多个中间件中的process_response() 方法是按照MIDDLEWARE中的注册顺序倒序执行的。它有两个参数,一个是request,一个是response,request是和process_request中一样的对象,response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpResponse对象。
          c. 中间件的process_request方法是在执行视图函数之前执行的。而process_response方法是在执行视图函数之后执行的。
          d. 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值执行。process_request是从前到后依次执行的;而process_response是从后到前依次执行的。
          e. 不同中间件之间传递的request和response都是同一个对象。(因此,如果在中间件这里截取到request和response却并不把它交给下一个环节,那么后面会接收不到request和response)
 
 
了解了两位守门大将之后,我们再来认识一下宫廷守卫(process_view())吧!
 
process_view的个人简介
process_view(self, request, view_func, view_args, view_kwargs)
request是HttpRequest对象。
view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
view_args是将传递给视图的位置参数的列表。
view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。
第一步:写入中间件到mymiddleware中
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
 
class test1(MiddlewareMixin):
    def process_request(self, request):
        print('-----test1-----request-----')
        return
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):    #处理视图的实例方法,它有五个参数
        print("-----test1-----view-----")    # 打印,用来分析执行顺序
        print(view_func, view_func.__name__)    #一个是打印参数view_func传递的是什么,另外打印它的__name__属性,也就是函数名
 
class test2(MiddlewareMixin):
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test2-----view-----")    #用来与test1中的view做对比
        print(view_func, view_func.__name__)
 
第二步:运行server,查看结果并分析
-----test1-----request-----
-----test2-----request-----    # 按正序运行,先运行test1的request,再运行test2的request
-----test1-----view-----    # test2中的request运行完成之后接着运行test1中的view!
<function home at 0x02CCB468> home    # 然后发现view_func传递的是一个函数的地址,这里函数是home函数(视图中自定义的函数,也就是request和response传递的对象)
-----test2-----view-----    # test1中的view运行结束之后,接着运行test2中的view,原来view和request一样,都是正向运行
<function home at 0x02CCB468> home    # test1中的view和test2中的view一样,但是想一下,这个处理view实例方法是干嘛的呢?它出现在request运行之后,在真正运行视图函数之前,emmmm...我也不知道这哥们是做什么的,暂且称它为宫廷守卫吧!
-----views-----    #这个是真正的视图函数执行的结果
-----test2-----response-----    # 接下来response按照倒序正常运行
-----test1-----response-----
 
第三步:分析
          a. 首先运行test1类中处理request的实例方法,打印出了-----test1-----request-----
          b. 然后运行test2类中处理request的实例方法,打印出了-----test2-----request-----
          c. 接着运行test1类中处理view的实例方法,打印出了-----test1-----view-----       
          d. 然后运行test2类中处理view的实例方法,打印出了-----test2-----view-----
          e. 接着运行到视图函数中,打印出了-----views-----
          f. 然后从视图函数中拿到response,开始往回走,由于来的时候先经过test1,在经过test2,所以在回去的时候,要先经过test2,再经过test1,因此打印出了-----test2-----response-----
          g. 走过test2的response之后,走到test1的response,打印出了-----test1-----response-----
          h. 顺利走过自定义的中间件所有流程之后,拿着response通过wsgi返回给浏览器
 
第四步:总结
          a. process_view方法是在process_request之后,视图函数之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的
          b. 它返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。
 
 
接下来要登场的是皇家守卫中..霸气侧漏大名鼎鼎的——医疗兵(processe_exception)(:D)
process_exception的个人简介
process_exception(self, request, exception)
request是HttpRequest对象。
exception是视图函数异常产生的Exception对象。
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
第一步:写入中间件到mymiddleware中
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
 
class test1(MiddlewareMixin):
    def process_request(self, request):
        print('-----test1-----request-----')
        return
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test1-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):    # 处理异常的实例方法,有两个参数
        print(exception)    # 打印exception这个参数,猜测是异常信息
        print("-----test1-----exception-----")    # 打印,分析运行顺序
        return HttpResponse(str(exception))    #由于抛出自定义异常会报错,我们在这里把异常截获并改变response
 
class test2(MiddlewareMixin):
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test2-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):    
        print(exception)    # 用来分析是否与test1中是同一个异常
        print("-----test2-----exception-----")    # 用来与test1做对比
 
第二步:由于process_exception只有当遇到视图函数中抛出异常时才做处理,因此我们手动抛出一个异常
# views.py
 
from django.shortcuts import render, redirect, HttpResponse
 
# Create your views here.
def home(request):
    print('-----views-----')
    raise ValueError("自定义异常")    #抛出自定义异常,ValueError
    return HttpResponse('-----home-----')
 
第三步:运行server,查看结果并分析
-----test1-----request-----
-----test2-----request-----
-----test1-----view-----
<function home at 0x02CCB468> home
-----test2-----view-----
<function home at 0x02CCB468> home
-----views-----    # 正常运行到视图函数中打印的部分
自定义异常    # 参数传递的是自定义异常
-----test2-----exception-----    # 可以看到先运行了test2中的exception
自定义异常    # test1和test2中的异常是一个异常,可以推断出,test2中捕获异常之后,把异常传递给了test1
-----test1-----exception-----    # process_exctption是倒序运行
-----test2-----response-----
-----test1-----response-----
 
第四步:分析
          a. 首先运行test1类中处理request的实例方法,打印出了-----test1-----request-----
          b. 然后运行test2类中处理request的实例方法,打印出了-----test2-----request-----
          c. 接着运行test1类中处理view的实例方法,打印出了-----test1-----view-----       
          d. 然后运行test2类中处理view的实例方法,打印出了-----test2-----view-----
          e. 接着运行到视图函数中,打印出了-----views-----
          f. 然后由于视图函数抛出异常,运行到test2类中处理exception的实例方法,打印出了-----test2-----exception----- 
          g. 接着运行到test1中处理exception的实例方法,打印出了-----test1-----exception----- 
          h. 然后从视图函数中拿到response,开始往回走,由于来的时候先经过test1,在经过test2,所以在回去的时候,要先经过test2,再经过test1,因此打印出了-----test2-----response-----
          i. 走过test2的response之后,走到test1的response,打印出了-----test1-----response-----
          j. 顺利走过自定义的中间件所有流程之后,拿着response通过wsgi返回给浏览器
 
第五步:总结
          a. 如果视图函数中无异常,process_exception方法不执行。
          b. 这里医疗兵也很容易理解对吧?(:D)
 
 
介绍了以上四员大将之后,最后一个要出场的家伙露面的机会不多(使用较少),但还是要介绍对吧!
process_template_response的个人简介
process_template_response(self, request, response)process_view(self, request, view_func, view_args, view_kwargs)
request是HttpRequest对象。
response是TemplateResponse对象(由视图函数或者中间件产生)。
view_args是将传递给视图的位置参数的列表。
view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
第一步:写入中间件到mymiddleware中
# mymiddleware.py
 
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
 
class test1(MiddlewareMixin):
    def process_request(self, request):
        print('-----test1-----request-----')
        return
    def process_response(self, request, response):
        print('-----test1-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test1-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):
        print(exception)
        print("-----test1-----exception-----")
        return HttpResponse(str(exception))
 
    def process_template_response(self, request, response):    # process_template_response实例化对象,有两个参数
        print("-----test1-----template-----")    #打印,用来分析顺序
        return response    #把response原路返回
 
class test2(MiddlewareMixin):
    def process_request(self, request):
        print('-----test2-----request-----')
        return
    def process_response(self, request, response):
        print('-----test2-----response-----')
        return response
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("-----test2-----view-----")
        print(view_func, view_func.__name__)
 
    def process_exception(self, request, exception):
        print(exception)
        print("-----test2-----exception-----")
 
    def process_template_response(self, request, response):
        print("-----test2-----template-----")    #用来与test1中的process_template_response做对比
        return response
 
第二步:由于process_template_response的触发需要一定的条件(视图函数返回的对象有一个render()方法)
# views.py
 
from django.shortcuts import render, redirect, HttpResponse
 
# Create your views here.
def home(request):
    print('-----views-----')
 
    def render():
        print("-----render-----")
        return HttpResponse("home")
 
    rep = HttpResponse("OK")
    rep.render = render    # 这步是通过看源码发现可以在外部给它添加实例化方法
    return rep
 
第三步:运行server,查看结果并分析
-----test1-----request-----
-----test2-----request-----
-----test1-----view-----
<function home at 0x02CCB0C0> home
-----test2-----view-----
<function home at 0x02CCB0C0> home
-----views-----    # 打印函数中的views
-----test2-----template-----    # 打印了test2中的process_template_response
-----test1-----template-----    # 打印了test1中的process_template_response
-----render-----    # 有意思的地方在这里,中间件执行完之后,又回到视图函数中执行render中的内容
-----test2-----response-----
-----test1-----response-----
 
第四步:分析
          a. 首先运行test1类中处理request的实例方法,打印出了-----test1-----request-----
          b. 然后运行test2类中处理request的实例方法,打印出了-----test2-----request-----
          c. 接着运行test1类中处理view的实例方法,打印出了-----test1-----view-----       
          d. 然后运行test2类中处理view的实例方法,打印出了-----test2-----view-----
          e. 接着运行到视图函数中,打印出了-----views-----
          f.  然后跳过render函数,运行test2类中的处理template_response的实例方法,打印出了-----test2-----template-----
          g. 接着运行test1类中的处理template_response的实例方法,打印出了-----test1-----template-----
          h. 然后回过头去运行render函数,打印出了-----render-----
          i. 然后从视图函数中拿到response,开始往回走,由于来的时候先经过test1,在经过test2,所以在回去的时候,要先经过test2,再经过test1,因此打印出了-----test2-----response-----
          j. 走过test2的response之后,走到test1的response,打印出了-----test1-----response-----
          k. 顺利走过自定义的中间件所有流程之后,拿着response通过wsgi返回给浏览器
 
第五步:总结
          a. 视图函数执行完之后,立即执行了中间件的process_template_response方法,顺序是倒序,先执行MD1的,在执行MD2的,接着执行了视图函数返回的HttpResponse对象的render方法,返回了一个新的HttpResponse对象,接着执行中间件的process_response方法。
 
 
 
三、中间件的执行顺序

1.request和response两兄弟的执行顺序
请求到达中间件之后,先按照正序执行每个注册中间件的process_reques方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法,将HttpResponse对象返回给浏览器。
也就是说:如MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。

 

 
2.两员守门大将(request、response)和宫廷守卫(view)的执行顺序
process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,继续按顺序执行,所有process_view方法执行完后执行视图函数。
假如中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,也就是中间件6的process_response方法开始倒序执行。
 

 

3.那么医疗兵(exception)和隐者(template)与它们之间又有什么关系呢?
它们两个需要一定的触发条件:exception需要视图函数抛出一个异常;template需要视图函数返回的对象中有一个render()方法。
a. 从下图可以看出,request如果返回none,则按照正常顺序从上到下(注册的顺序)依次执行;
b. 如果在某个中间件中的request中出现了HttpResponse,那么就从这个中间件的处理response的方法倒序往回执行;
c. request和view都是返回None才正常执行,而response是返回response才正常执行;
d. 如果某个中间件的处理视图的实例化方法返回了HttpResponse,那么就不再执行后续中间件的处理view的方法,而是直接走到处理response,按照倒序往回执行
 
a. 下面箭头向下的有request和view,说明只有它们两个是从上往下正常执行的;
b. template和response用了虚线,表示两者的触发需要一定的条件;

 

 
4.对比一下皇家守卫团的成员们!
 
 
执行顺序
何时执行
返回值
process_request
按照注册顺序
请求从wsgi拿到之后
返回None,继续执行后续的中间件的process_request方法
返回response , 不执行后续的中间件的process_request方法
process_response
按照注册顺序的倒序
请求有响应的时候
必须返回一个response对象
process_view
按照注册顺序
在urls.py中找到对应关系之后 
在执行真正的视图函数之前
返回None,继续执行后续的中间件的process_view方法
返回response,不执行后续中间件的process_view方法,
从最后一个process_response返回
process_exception
按照注册顺序的倒序
视图函数中抛出异常的时候才执行
返回None,继续执行后续中间件的process_exception
返回response,
process_template_response
按照注册顺序的倒序
视图函数执行完,
在执行视图函数返回的响应对象的render方法之前
返回None,继续执行后续中间件的process_exception
返回response,
    
 
5.接下来让我们来完整的看一下Django的流程吧!

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
posted @ 2018-07-01 18:31  小隐于林i  阅读(134)  评论(0编辑  收藏  举报