Django中间件
为什么使用中间件
比如,当每个用户访问一个路由时都需要判断这个用户是否已登录,在学习中间件以前,我们使用的是装饰器的方式,为每个函数添加装饰器。但如果我们的系统界面很多,功能很多,为每个系统都添加装饰器也会比较繁琐,这时就可以使用中间件来实现。
中间件介绍
-
中间件就是用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
-
django的执行顺序是,当一个请求进来以后,会先去中间件中核对各项策略,之后才会执行后面的程序之类的(像是门卫)。所以我们如果有些功能需要对输入/输出中进行改变,就可以使用到中间件。
-
注意:因为中间件是影响全局,使用不当会引起性能问题,所以请谨慎使用。
-
其实我们一直在使用中间件,在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',
]
自定义中间件
- 创建目录(也可以直接创建py文件),比如在app01下创建了mymiddleware目录
- 创建py文件:app01middle(自定义名称)
- 根据查看其他中间件源码得出结果,都需要创建一个类,继承MiddlewareMixin,还需要导入一个模块:from django.utils.deprecation import MiddlewareMixin
- 根据源码可得,类中可以定义五个方法
# 掌握
process_request
process_response
# 了解(大概概念即可)(详情见后文)
process_view
process_template_response
process_exception
process_request
- process_request 特性:
- 请求来的时候,会按settings.py的MIDDLEWARE列表中,从上到下依次执行注册的中间件定义的方法
- 如果在该方法中返回了HttpResponse对象,那么不再往后执行视图函数中的内容,直接原路返回。
- 需要传入一个request额外参数def process_response(self, request):
- 如果没有定义process_request,会直接跳过
验证执行顺序:
# 1. 在app01middle下添加内容
class MyMiddleware01(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware01 process_request')
class MyMiddleware02(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware02 process_request')
# 2. 在settings.py中注册到中间件中,添加下面两项内容
'app01.mymiddleware.app01middle.MyMiddleware01',
'app01.mymiddleware.app01middle.MyMiddleware02',
# 3. 配置路由层
path('index/', views.index_func),
# 4. 配置视图层
def index_func(request):
print('from index view')
return HttpResponse('index view')
-
- 访问index路由,后台返回信息
from MyMiddleware01 process_request
from MyMiddleware02 process_request
from index view
process_request
- process_response特性:
- 需要给两个参数 def process_response(self, request, response)。该方法有两个request和response两个形参,response指的是后端想要返回给前端浏览器的数据,该方法必须返回该形参,也可以替换。
- 请求走的时候,会按settings.py的MIDDLEWARE列表中,从下到上依次执行注册的中间件定义的方法
- 如果没有定义process_response,会直接跳过
验证执行顺序:
class MyMiddleware01(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware01 process_request')
return HttpResponse('被MyMiddleware01拦截')
class MyMiddleware02(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware02 process_request')
这时再访问index路由,返回:from MyMiddleware01 process_request,没有再向下执行MyMiddleware02中定义的方法,也没有执行views.py中定义的函数
页面返回:被MyMiddleware01拦截
验证process_response
定义中间件
class MyMiddleware01(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware01 process_request')
# return HttpResponse('被MyMiddleware01拦截')
def process_response(self, request, response):
print('from MyMiddleware01 process_response')
return response
class MyMiddleware02(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware02 process_request')
def process_response(self, request, response):
print('from MyMiddleware02 process_response')
return response
返回值
# 可以明显看到返回值是由上到下执行request,当执行完视图函数中内容后,再以相反的顺序执行response
from MyMiddleware01 process_request
from MyMiddleware02 process_request
from index view
from MyMiddleware02 process_response
from MyMiddleware01 process_response
如果被process_request拦截了,还会走process_response吗?
class MyMiddleware01(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware01 process_request')
return HttpResponse('被MyMiddleware01拦截') # 如果这里定义了HttpResponse后,
def process_response(self, request, response):
print('from MyMiddleware01 process_response')
return response
class MyMiddleware02(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleware02 process_request')
def process_response(self, request, response):
print('from MyMiddleware02 process_response')
return response
根据下面的返回值,可以看到,如果此中间件定义了response的话,也会执行response的(django是这样配置的,其他框架不一定)
from MyMiddleware01 process_request
from MyMiddleware01 process_response
process_view
- 特性:
- 路由匹配成功之后,执行视图函数或视图类之前,自动触发。
- 执行顺序同process_request
- 参数说明:
view_func代表要执行哪个视图函数
view_args与view_kwargs代表执行的视图函数对应的参数
class MyMiddleware01(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
print('from MyMiddleware01 process_view')
process_exception
- 特性
- 当执行视图函数过程中出现异常时(也就是报错)就会触发。
- 形参exception就是报错信息
class MyMiddleware01(MiddlewareMixin):
def process_exception(self, request, exception):
print('from MyMiddleware01 process_exception')
# 视图层
def index_func(request):
jkfldalda # 这时process_exception才会返回这一行的报错信息
return HttpResponse('我是index')
process_template_response(基本不会用)
- 特性
- 需要返回response
- 当所执行的视图函数里面的HttpResponse里面,含有render属性,并且render是一个方法的时候才可以执行
触发方法如下:
class MyMiddleware01(MiddlewareMixin):
def process_template_response(self, request, response):
print('from MyMiddleware01 process_template_response')
return response
# 视图层
def index_func(request):
print('from index func')
def render():
return HttpResponse('定义了render方法才会运行process_template_response中间件')
obj = HttpResponse('index view')
obj.render = render
return obj
基于Django中间件的功能设计
- Django将各个功能制作成配置文件(settings.py)的字符串形式,当把功能配置到配置文件中后,就实现了可插拔设计:
- 如果想要开启该功能,就将该功能字符串配置进配置文件中。
- 如果要关闭该功能,就将该字符串所属配置注释。
功能的插拔式设计
使用字符串形式导入模块
# 1. 先建立测试目录/test1/test2/test.py
desc = 'directory:/test1/test2/test.py'
# 2. 在项目根目录下随便一个py文件中引用desc变量
# 2.1 正常形式:
from test1.test2.test import desc
print(desc)
# 2.2 利用字符串导入模块
# 2.2.1.定义一个字符串,字符串内容为要导入模块的路径
s1 = "test1.test2.test"
# 2.2.2.导入相关模块
import importlib # 这个包可以让字符串拆分成导模块的形式
res = importlib.import_module(s1)
print(res) # 返回值<module 'test1.test2.test' from 'D:\\xxxx\\xxxx\\xxxx\\test1\\test2\\test.py'>
-
最小单位到py文件级别,不能是py文件中的变量名!!!
-
总结:importlib模块可以让你使用点的方式,导入一个模块,所以就可以根据这个特性设计一个可插拔的程序。
-
有了上面的铺垫,我们自定义一个可插拔式项目
需求:
1. 可插拔式设计
2. 需要执行start.py时,统一发送消息
设计:
1. 建立一个程序目录notify,所有的程序都放到这个目录中。
2. 添加一个settings.py配置文件,配置文件用于注册功能,未来添加功能时就可以修改配置文件
3. 在notify目录中添加一个__init__.py文件,在导入这个目录时,会自动执行这个文件中的所有程序。
4. 添加一个start.py启动程序,此程序导入notify目录与
实施:
-
- notify目录以及目录中内容
email.py 用于模拟发送email信息
- notify目录以及目录中内容
class Email(object):
def __init__(self):
pass
def send_msg(self, content):
print(f'来自Email的消息>>>:{content}')
qq.py 用于模拟发送qq信息
class Qq(object):
def __init__(self):
pass
def send_msg(self, content):
print(f'来自QQ的消息>>>:{content}')
wechat.py 用于模拟发送微信信息
class Wechat(object):
def __init__(self):
pass
def send_msg(self, content):
print(f'来自wechat的消息>>>{content}')
-
- settings.py中进行注册
NOTIFY_LIST = [
'notify.email.Email',
'notify.qq.Qq',
'notify.wechat.Wechat',
]
-
- 配置项目目录中创建初始化__init__.py
# import包名的时候,会先执行包下面的__init__.py中的所有内容
"""
NOTIFY_LIST: 为settings.py中定义的中间件列表
"""
from settings import NOTIFY_LIST
import importlib
def send_all(content):
# 获取settings.py中的每个NOTIFY_LIST中的每个值
for full_path in NOTIFY_LIST:
# 将NOTIFY_LIST中的每个值进行一次切分,得到的结果:module_path=notify.email class_str_name=Email
# 这样就得到了包路径与类名
# module_path属于包路径,这样就可以使用importlib进行导入了
# class_str_name是类名
module_path, class_str_name = full_path.rsplit('.', maxsplit=1)
# 使用importlib导入包,下面这句话的意思就是 from notify import email
module_name = importlib.import_module(module_path)
# 利用反射从模块中获取字符串对应的类名
class_name = getattr(module_name, class_str_name)
'''
class_name为:
<class 'notify.email.Email'>
<class 'notify.qq.Qq'>
<class 'notify.wechat.Wechat'>
'''
# 利用类名加括号产生对象
obj = class_name()
# 对象调用发送消息方法
obj.send_msg(content)
-
- 设置启动文件start.py
import notify
if __name__ == '__main__':
# 跟一个包要一个方法,其实就是跟__init__.py要方法,所以只要在__init__.py中定义一个send_all函数就可以了
notify.send_all('我是要发送的消息')
当需要新添加需求时,只需要在notify项目目录中添加新功能,并且类中的功能名需要与原其它功能相同,之后在settings.py中进行注册,就可以使用了。
以上只是为了测试实现方式,写得比较简单,在实际生产中肯定会更加复杂。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类