忠诚的皇家守卫团——中间件
一、什么是中间件呢?
二、中间件的五员大将
三、中间件的执行流程
一、什么是中间件呢?
枯燥的中间件概念
按照官方的说法,中间件是一个用来处理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的流程吧!