Django框架—中间件Middleware
1.为什么使用中间件
之前我们在判断用户是否登录时,是通过给视图函数加装饰器来实现让没有登录的用户直接跳转到登录页面去登陆。
如果当需要我们判断的视图函数数量巨大,那么我们需要给所有的视图函数手动添加装饰器,过程稍微有点繁琐。
这里我们通过django中的中间件可以实现为所有请求增加相同的验证操作。
2.中间件介绍
中间件(middleware)顾名思义,是介于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.
说的直白一点,中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
Django项目中,我们的请求和响应一直都在使用中间件,只是我们没有注意,Django项目路径下的Settings.py文件,有一项MIDDLEWARE配置,是django默认自带的中间件:
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', ]
MIDDLEWARE配置项是一个列表,里面的字符串就是一个个中间件,本质是一个个类,通过路径反射类来调用执行。
Django请求响应的生命周期
在Django项目中,浏览器的每一次请求经历的过程如图:
-
经过wsgi运行socket接受请求,并按http协议拆包获取数据。
-
正向一次经过中间件中的process_reqeust方法处理。
-
经过路由分发调用对应的视图函数,去数据库获取响应数据,并渲染到页面,返回HttpResponse响应对象。
-
HttpResponse响应倒序经过中间件中的process_response方法处理,最终返回给客户端。
二、自定义中间件
想了解中间件,可以查看开源中国更多详细解释
1.中间件可重写的方法
中间件可重写的5个方法:重点(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对象,则直接将该对象返回给用户。
当用户发起请求的时候会依次经过所有的的中间件,这个时候的请求时process_request,最后到达views的函数中,views函数处理后,在依次穿过中间件,这个时候是process_response,最后返回给请求者。
2.中间件使用
自定义中间件
在项目中路径下创建一个包,名字任意,一般起名为utils,表示共用的组件,utils文件中创建一个py文件,名字任意,如:middlewares.py
-
自定义中间件的类需要继承MiddlewareMixin
实现一个验证登录的中间件类
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect from django.urls import reverse # 定义一个中间件,继承MiddlewareMixin class Middleware1(MiddlewareMixin): def process_request(self,request): """ http请求会执行的方法,中间件中的process_request方法会挨个执行 :param request: wsgi封装的request数据,和视图函数中的一样 :return: 不填返回none,正常执行;填HttpResponse直接返回,不往下继续执行 """ url = ["/login/"] # 设置白名单,login请求不需要验证是否登录 if request.path not in url: # 不在白名单中的视图函数请求,需要认证是否登录 if not request.session.get("user"): return redirect(reverse("login")) print("Middleware1中的process_request") def process_response(self,request,response): """ http响应会执行的方法,中间件中的process_request方法会挨个执行 :param request: wsgi封装的request数据 :param response: 返回的HttpResponse对象,类似接力棒一样,一级一级传递返回 :return: 返回给下一级中间件process_response方法的HttpResponse对象 """ print("Middleware1中的process_response") return response
settings中配置自定义中间件
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.Middleware1', # 自定义的验证登录中间件 ]
process_request
process_request(self,request);request参数,和视图函数中request一致,封装好的请求数据。
返回值:None或HttpResponse
-
是None的话,按正常流程继续走,交给下一个中间件处理
-
如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
多个中间件中process_request方法的执行过程
视图函数login
def login(request): if request.method == "POST": if request.POST.get("username")=="alex" and request.POST.get("password") == "alex": request.session["user"]="alex" return redirect("home") else: return redirect("login") print("--> 请求视图函数") return render(request,"login.html")
middlewares.py文件
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self,request): print(">>> MD1的process_request") class MD2(MiddlewareMixin): def process_request(self,request): print(">>> MD2的process_request")
settings.py中配置自定义中间件
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', 'middlewares.MD1', # 自定义中间件MD1,这个写的是你项目路径下的一个路径,例如,如果你放在项目下,文件夹名成为utils,那么这里应该写utils.middlewares.MD1 'middlewares.MD2' # 自定义中间件MD2 ]
访问时,结果如下
>>> MD1的process_request >>> MD2的process_request --> 请求视图函数
解析:
-
中间件的process_request方法是在执行视图函数之前执行的
-
当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
-
不同中间件之间传递的request都是同一个对象
process_response
process_response(self, request, response);
参数:
-
一个是request,请求体数据;
-
一个是response,response是视图函数返回的HttpResponse对象。
-
函数的返回值必须是HttpResponse对象
多个中间件中process_response方法的执行过程
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self,request): print(">>> MD1的process_request") def process_response(self,request,response): print("<<< MD1中的process_response ") # print(response.__dict__['_container'][0].decode('utf-8')) # 获取响应体中的内容 return response # 必须有响应值,类似接力一样传到最后一层 class MD2(MiddlewareMixin): def process_request(self,request): print(">>> MD2的process_request") def process_response(self, request, response): print("<<< MD2中的process_response ") return response #必须返回response,不然你上层的中间件就没有拿到httpresponse对象,就会报错
访问视图函数,打印结果如下:
>>> MD1的process_request >>> MD2的process_request --> 请求视图函数 <<< MD2中的process_response <<< MD1中的process_response
解析:
-
process_response方法是在视图函数之后执行的
-
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的。
-
不同的process_response方法,一次传递视图函数返回的HttpResponse对象。
如果我们在。
request中主动返回HttpResponse运行过程
我们在MD1中的process_request方法中返回一个自己的HttpResponse对象,看请求执行的流程
class MD1(MiddlewareMixin): def process_request(self,request): print(">>> MD1的process_request") return HttpResponse("响应出现了问题") # 返回自定的HttpResponse def process_response(self,request,response): print("<<< MD1中的process_response ") # print(response.__dict__['_container'][0].decode('utf-8')) # 获取响应体中的内容 return response # 必须有响应值,类似接力一样传到最后一层
打印结果如下:
>>> MD1的process_request
<<< MD1中的process_response
执行过程图解:
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)。
多个中间件中process_view方法的执行过程
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self,request): print(">>> MD1的process_request") def process_response(self,request,response): print("<<< MD1中的process_response ") return response # 必须有响应值,类似接力一样传到最后一层 def process_view(self,request,view_func,view_args,view_kwargs): print('-'*40) print(">>> MD1中的process_views") print(view_func,view_func.__name__) # 打印视图函数和名字 class MD2(MiddlewareMixin): def process_request(self,request): print(">>> MD2的process_request") def process_response(self, request, response): print("<<< MD2中的process_response ") return response #必须返回response,不然你上层的中间件就没有拿到httpresponse对象,就会报错 def process_view(self,request,view_func,view_args,view_kwargs): print('-' * 40) print(">>> MD2中的process_views") print(view_func, view_func.__name__)
打印结果如下
>>> MD1的process_request
>>> MD2的process_request
----------------------------------------
>>> MD1中的process_views
<function login at 0x000002DCB5224AE8> login
----------------------------------------
>>> MD2中的process_views
<function login at 0x000002DCB5224AE8> login
--> 请求视图函数
<<< MD2中的process_response
<<< MD1中的process_response
分析:
-
process_view方法是在process_request之后,视图函数之前执行的。
-
执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的
-
如果process_view中返回了HttpResponse对象,则跳过后面的process_view中间件,直接执行所有实现了process_response方法的中间件
MD1中返回HttpResponse对象执行流程
>>> MD1的process_request >>> MD2的process_request ---------------------------------------- >>> MD1中的process_views <function login at 0x0000023400004AE8> login <<< MD2中的process_response <<< MD1中的process_response
执行流程图解
process_exception
process_exception(self, request, exception);
参数:
-
一个HttpRequest对象
-
一个exception是视图函数异常产生的Exception对象。
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。
如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。
如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
多个中间件中process_exception方法的执行过程
如果视图函数没有抛出异常,不会执行,所以模拟视图函数中抛出异常:login函数中
raise ValueError("呵呵")
中间件写法
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self,request): print(">>> MD1的process_request") def process_response(self,request,response): print("<<< MD1中的process_response ") return response # 必须有响应值,类似接力一样传到最后一层 def process_view(self,request,view_func,view_args,view_kwargs): print(">>> MD1中的process_views") def process_exception(self,request,exception): print(exception) # 打印异常 print(">>> MD1中的process_exception") class MD2(MiddlewareMixin): def process_request(self,request): print(">>> MD2的process_request") def process_response(self, request, response): print("<<< MD2中的process_response ") return response #必须返回response,不然你上层的中间件就没有拿到httpresponse对象,就会报错 def process_view(self,request,view_func,view_args,view_kwargs): print(">>> MD2中的process_views") def process_exception(self,request,exception): print(exception) # 打印异常 print(">>> MD2中的process_exception")
打印结果如下:
>>> MD1的process_request >>> MD2的process_request >>> MD1中的process_views >>> MD2中的process_views --> 请求视图函数 呵呵 >>> MD2中的process_exception 呵呵 >>> MD1中的process_exception ValueError: 呵呵 终端的报错信息,省略写法 <<< MD2中的process_response <<< MD1中的process_response
如果在MD2中的process_exception中放回一个响应对象
class MD2(MiddlewareMixin): def process_request(self,request): print(">>> MD2的process_request") def process_response(self, request, response): print("<<< MD2中的process_response ") return response #必须返回response,不然你上层的中间件就没有拿到httpresponse对象,就会报错 def process_view(self,request,view_func,view_args,view_kwargs): # print('-' * 40) print(">>> MD2中的process_views") # print(view_func, view_func.__name__) def process_exception(self,request,exception): print(exception) # 打印异常 print(">>> MD2中的process_exception") return HttpResponse(str(exception)) # 返回一个响应对象
直接结果如下:
>>> MD1的process_request >>> MD2的process_request >>> MD1中的process_views >>> MD2中的process_views --> 请求视图函数 呵呵 >>> MD2中的process_exception <<< MD2中的process_response <<< MD1中的process_response
分析:
-
process_exception方法是视图函数出错才会执行
-
执行顺序按照MIDDLEWARE中的注册顺序从后到前顺序执行的
-
如果process_exception方法返回了HttpResponse对象,那么会跳过前面的process_exception中间件,直接执行process_response
图解过程
process_template_response(用的比较少)
process_template_response(self, request, response);
参数:
-
一个HttpRequest对象
-
response是TemplateResponse对象(由视图函数或者中间件产生)。
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
视图函数login中,为HttpResponse对象添加方法
def login(request): if request.method == "POST": if request.POST.get("username")=="alex" and request.POST.get("password") == "alex": request.session["user"]="alex" return redirect("home") else: return redirect("login") print("--> 请求视图函数") def render(): print("in index/render") return HttpResponse("O98K") # 返回的将是这个新的对象,会替换视图函数的HttpResponse对象 ret = HttpResponse("OK") ret.render = render return ret
中间件文件
class MD1(MiddlewareMixin): def process_request(self, request): print("MD1里面的 process_request") def process_response(self, request, response): print("MD1里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") return HttpResponse(str(exception)) def process_template_response(self, request, response): print("MD1 中的process_template_response") return response class MD2(MiddlewareMixin): def process_request(self, request): print("MD2里面的 process_request") pass def process_response(self, request, response): print("MD2里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD2 中的process_exception") def process_template_response(self, request, response): print("MD2 中的process_template_response") return response
打印结果如下:
>>> MD1的process_request >>> MD2的process_request >>> MD1中的process_views >>> MD2中的process_views --> 请求视图函数 MD2 中的process_template_response MD1 中的process_template_response in index/render <<< MD2中的process_response <<< MD1中的process_response
分析:
-
process_template_response方法在视图执行完之后
-
执行顺序是从后往前倒序执行。
-
最后返回的HttpResponse是执行视图函数的render方法返回的HttpResponse对象
注意process_response方法执行的过程
process_response方法只会判断视图函数返回的HttpResponse对象中有没有render方法(但不会立即执行render方法)。
如果HttpResponse对象有,执行process_response方法,再接力传到上一层的中间件的process_response方法,同样也会执行process_response。
直到传递到最外一层,执行完对应process_response后,才会调用HttpResponse对象中的render()方法,render方法返回的HttpResponse对象会将视图函数返回的HttpResponse对象替换掉发送个客户端。