Django中间件
1.什么是中间件
官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
2. 中间件最关键的顺序问题
MIDDLEWARE
的顺序很重要,具有先后关系,因为有些中间件会依赖其他中间件。例如: AuthenticationMiddleware
需要在会话中间件中存储的经过身份验证的用户信息,因此它必须在 SessionMiddleware
后面运行 。
在请求阶段,调用视图之前,Django 按照定义的顺序执行中间件 MIDDLEWARE
,自顶向下。
你可以把它想象成一个洋葱
:每个中间件类都是一个“皮层”,它包裹起了洋葱的核心--实际业务视图
。如果请求通过了洋葱的所有中间件
层,一直到内核的视图,那么响应将在返回的过程中以相反的顺序再通过每个中间件层,最终返回给用户。
如果某个层的执行过程认为当前的请求应该被拒绝,或者发生了某些错误,导致短路,直接返回了一个响应,那么剩下的中间件以及核心的视图函数都不会被执行。
3. 自定义中间件
3.1自定义中间件流程
3.1.1 在project下创建一个文件夹,文件夹下随便创建一个py文件
middleware/m1.py
from django.utils.deprecation import MiddlewareMixin
class Middle1(MiddlewareMixin):
def process_request(self, request):
print("来了")
def process_response(self, request, response):
print('走了')
return response process_response必须返回response
3.1.2 在setings文件中注册这个py文件
django项目的settings模块中,有一个 MIDDLEWARE_CLASSES 变量,其中每一个元素就是一个中间件
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',
'middleware.m1.Middle1',
]
3.2 中间件定义方法
3.2.1 传统的方法:五大钩子函数
传统方式自定义中间件其实就是在编写五大钩子函数:
- process_request(self,request)
- process_view(self, request, callback, callback_args, callback_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
可以实现其中的任意一个或多个!
钩子函数 | 执行时机 | 执行顺序 | 返回值 |
---|---|---|---|
process_request | 请求刚到来,执行视图之前 | 配置列表的正序 | None或者HttpResponse对象 |
process_response | 视图执行完毕,返回响应时 | 逆序 | HttpResponse对象 |
process_view | process_request之后,路由转发到视图,执行视图之前 | 正序 | None或者HttpResponse对象 |
process_exception | 视图执行中发生异常时 | 逆序 | None或者HttpResponse对象 |
process_template_response | 视图刚执行完毕,process_response之前 | 逆序 | 实现了render方法的响应对象 |
1. process_request(self,request)
只有一个参数,也就是request请求内容,和视图函数中的request是一样的。所有的中间件都是同样的request,不会发生变化。它的返回值可以是None也可以是HttpResponse对象。返回None的话,表示一切正常,继续走流程,交给下一个中间件处理。返回HttpResponse对象,则发生短路,不继续执行后面的中间件,也不执行视图函数,而将响应内容返回给浏览器。
2. process_response(self, request, response)
有两个参数,request和response。request是请求内容,response是视图函数返回的HttpResponse对象。该方法的返回值必须是一个HttpResponse对象,不能是None。
process_response()方法在视图函数执行完毕之后执行,并且按配置顺序的逆序执行。
3. process_view(self, request, callback, callback_args, callback_kwargs)
- request:HttpRequest对象。
- view_func:真正的业务逻辑视图函数(不是函数的字符串名称)。
- view_args:位置参数列表
- view_kwargs:关键字参数字典
请务必牢记:process_view()在Django调用真正的业务视图之前被执行,并且以正序执行。当process_request()正常执行完毕后,会进入urlconf路由阶段,并查找对应的视图,在执行视图函数之前,会先执行process_view()中间件钩子。
这个方法必须返回None
或者一个 HttpResponse对象。如果返回的是None,Django将继续处理当前请求,执行其它的 process_view()中间件钩子,最后执行对应的视图。如果返回的是一个 HttpResponse对象,Django不会调用业务视图,而是执行响应中间件,并返回结果。
4. process_exception(request, exception)
- request
:
HttpRequest对象 - exception:视图函数引发的具体异常对象
当一个视图在执行过程中引发了异常,Django将自动调用中间件的 process_exception()
方法。 process_exception() 要么返回一个 None,要么返回一个 HttpResponse对象。如果返回的是HttpResponse对象 ,模板响应和响应中间件将被调用 ,否则进行正常的异常处理流程。
同样的,此时也是以逆序的方式调用每个中间件的process_exception方法,以短路的机制。
5. process_template_response(request, response)
request:HttpRequest对象
response :TemplateResponse对象
process_template_response() 方法在业务视图执行完毕后调用。
正常情况下一个视图执行完毕,会渲染一个模板,作为响应返回给用户。使用这个钩子方法,你可以重新处理渲染模板的过程,添加你需要的业务逻辑。
对于 process_template_response()方法,也是采用逆序的方式进行执行的。
3.2.2 钩子方法执行流程
(注:所有图片来自网络,侵删!)
一个理想状态下的中间件执行过程,可能只有process_request()和process_response()方法,其流程如下:
一旦任何一个中间件返回了一个HttpResponse对象,立刻进入响应流程!要注意,未被执行的中间件,其响应钩子方法也不会被执行,这是一个短路,或者说剥洋葱的过程。
如果有process_view方法的介入,那么会变成下面的样子:
总的执行流程和机制如下图所示:
仔细研究一下下面的执行流程,能够加深你对中间件的理解。
实例演示
介绍完了理论,下面通过实际的例子来演示一下。
要注意,之所以被称为传统的方法,是因为这里要导入一个将来会被废弃的父类,也就是:
from django.utils.deprecation import MiddlewareMixin
deprecation
是废弃、贬低、折旧、反对的意思,也就是说,这个MiddlewareMixin
类将来应该会被删除!
我们看一下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'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
这个类并没有自己定义五大钩子方法,而是定义了__call__
方法,通过hasattr的反射,寻找process_request
等钩子函数是否存在,如果存在就执行。它的本质和后面要介绍的Django官网提供的例子,也就是新的写法是一样的!
现在,假设我们有一个app叫做midware,在其中创建一个middleware.py模块,写入下面的代码:
from django.utils.deprecation import MiddlewareMixin
class Md1(MiddlewareMixin):
def process_request(self,request):
print("Md1处理请求")
def process_response(self,request,response):
print("Md1返回响应")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("Md1在执行%s视图前" %view_func.__name__)
def process_exception(self,request,exception):
print("Md1处理视图异常...")
class Md2(MiddlewareMixin):
def process_request(self,request):
print("Md2处理请求")
def process_response(self,request,response):
print("Md2返回响应")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("Md2在执行%s视图前" %view_func.__name__)
def process_exception(self,request,exception):
print("Md2处理视图异常...")
然后,我们就可以在setting.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',
'midware.middleware.Md1',
'midware.middleware.Md2',
]
在midware/views.py中创建一个简单的视图:
from django.shortcuts import render, HttpResponse
def mid_test(request):
print('执行视图mid_test')
raise
return HttpResponse('200,ok')
其中的raise可以用来测试process_exception()钩子。
编写一条urlconf,用来测试视图,比如:
from midware import views as mid_views
urlpatterns = [
path('midtest/', mid_views.mid_test),
]
重启服务器,访问...../midtest/
,可以在控制台看到如下的信息:
Md1处理请求
Md2处理请求
Md1在执行mid_test视图前
Md2在执行mid_test视图前
执行视图mid_test
Md2返回响应
Md1返回响应
3.2.3 Django官方方法
在Django的官方文档中(当前2.2),我们可以看到一种完全不同的编写方式。
这种编写方式省去了process_request()
和process_response()
方法的编写,将它们直接集成在一起了。
这种方式是官方推荐的方式!
中间件本质上是一个可调用的对象(函数、方法、类),它接受一个请求(request),并返回一个响应(response)或者None,就像视图一样。其初始化参数是一个名为get_response
的可调用对象。
中间件可以被写成下面这样的函数(下面的语法,本质上是一个Python装饰器,不推荐这种写法):
def simple_middleware(get_response):
配置和初始化
def middleware(request):
在这里编写具体业务视图和随后的中间件被调用之前需要执行的代码
response = get_response(request)
在这里编写视图调用后需要执行的代码
return response
return middleware
或者写成一个类(真.推荐形式),这个类的实例是可调用的,如下所示:
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
配置和初始化
def __call__(self, request):
在这里编写视图和后面的中间件被调用之前需要执行的代码
这里其实就是旧的process_request()方法的代码
response = self.get_response(request)
在这里编写视图调用后需要执行的代码
这里其实就是旧的process_response()方法的代码
return response
(是不是感觉和前面的MiddlewareMixin
类很像?)
Django 提供的 get_response
方法可能是一个实际视图(如果当前中间件是最后列出的中间件),或者是列表中的下一个中间件。我们不需要知道或关心它到底是什么,它只是代表了下一步要进行的操作。
两个注意事项:
- Django仅使用
get_response
参数初始化中间件,因此不能为__init__()
添加其他参数。 - 与每次请求都会调用
__call__()
方法不同,当 Web 服务器启动后,__init__()
只被调用一次。
实例演示
我们只需要把前面的Md1和Md2两个中间件类修改成下面的代码就可以了:
class Md1:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
print("Md1处理请求")
response = self.get_response(request)
print("Md1返回响应")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("Md1在执行%s视图前" %view_func.__name__)
def process_exception(self,request,exception):
print("Md1处理视图异常...")
class Md2:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
print("Md2处理请求")
response = self.get_response(request)
print("Md2返回响应")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("Md2在执行%s视图前" % view_func.__name__)
def process_exception(self, request, exception):
print("Md2处理视图异常...")
可以看到,我们不再需要继承MiddlewareMixin
类。
实际执行结果是一样的。
中间件应用场景
由于中间件工作在 视图函数执行前、执行后(像不像所有视图函数的装饰器!)适合所有的请求/一部分请求做批量处理
1、做IP限制
放在 中间件类的列表中,阻止某些IP访问了;
2、URL访问过滤
如果用户访问的是login视图(放过)
如果访问其他视图(需要检测是不是有session已经有了放行,没有返回login),这样就省得在 多个视图函数上写装饰器了!
3、缓存(还记得CDN吗?)
客户端请求来了,中间件去缓存看看有没有数据,有直接返回给用户,没有再去逻辑层 执行视图函数