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',
]

自定义中间件

  1. 创建目录(也可以直接创建py文件),比如在app01下创建了mymiddleware目录
  2. 创建py文件:app01middle(自定义名称)
  3. 根据查看其他中间件源码得出结果,都需要创建一个类,继承MiddlewareMixin,还需要导入一个模块:from django.utils.deprecation import MiddlewareMixin
  4. 根据源码可得,类中可以定义五个方法
# 掌握
process_request
process_response
# 了解(大概概念即可)(详情见后文)
process_view
process_template_response
process_exception

process_request

  • process_request 特性:
  1. 请求来的时候,会按settings.py的MIDDLEWARE列表中,从上到下依次执行注册的中间件定义的方法
  2. 如果在该方法中返回了HttpResponse对象,那么不再往后执行视图函数中的内容,直接原路返回。
  3. 需要传入一个request额外参数def process_response(self, request):
  4. 如果没有定义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')
    1. 访问index路由,后台返回信息
from MyMiddleware01 process_request
from MyMiddleware02 process_request
from index view

process_request

  • process_response特性:
  1. 需要给两个参数 def process_response(self, request, response)。该方法有两个request和response两个形参,response指的是后端想要返回给前端浏览器的数据,该方法必须返回该形参,也可以替换。
  2. 请求走的时候,会按settings.py的MIDDLEWARE列表中,从下到上依次执行注册的中间件定义的方法
  3. 如果没有定义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

  • 特性:
  1. 路由匹配成功之后,执行视图函数或视图类之前,自动触发。
  2. 执行顺序同process_request
  3. 参数说明:
    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

  • 特性
  1. 当执行视图函数过程中出现异常时(也就是报错)就会触发。
  2. 形参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(基本不会用)

  • 特性
  1. 需要返回response
  2. 当所执行的视图函数里面的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. 如果想要开启该功能,就将该功能字符串配置进配置文件中。
  2. 如果要关闭该功能,就将该字符串所属配置注释。

功能的插拔式设计

使用字符串形式导入模块

# 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目录与

实施:

    1. notify目录以及目录中内容
      email.py 用于模拟发送email信息
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}')
    1. settings.py中进行注册
NOTIFY_LIST = [
    'notify.email.Email',
    'notify.qq.Qq',
    'notify.wechat.Wechat',
]
    1. 配置项目目录中创建初始化__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)
    1. 设置启动文件start.py
import notify


if __name__ == '__main__':
    # 跟一个包要一个方法,其实就是跟__init__.py要方法,所以只要在__init__.py中定义一个send_all函数就可以了
    notify.send_all('我是要发送的消息')

当需要新添加需求时,只需要在notify项目目录中添加新功能,并且类中的功能名需要与原其它功能相同,之后在settings.py中进行注册,就可以使用了。
以上只是为了测试实现方式,写得比较简单,在实际生产中肯定会更加复杂。

posted @   树苗叶子  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示