Bootstrap

88.Django中间件的说明与使用方法

1. 概述

​ AOP(Aspect Oriented Programming ),面向切面编程,是对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。可以实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

​ 面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事物,日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使业务逻辑变得混杂不清。
举个例子:银行系统取款会有一个流程查询也会有一个流程。

在这里插入图片描述

​ Django的中间件,就是应用AOP技术来实现的,它是django请求/响应处理的钩子框架,是一个轻巧的低级“插件”系统,在不修改django项目原有代码的基础上,可以全局改变django的输入或输出,每个中间件组件负责执行某些特定功能。

​ PS:因为中间件改变的是全局(加入到整个流程),所以需要谨慎实用,滥用的话,会影响到服务器的性能。同样的,使用装饰器也可以达到这个效果,但是使用装饰器的话需要依次添加,代码会变得很复杂

2. django默认中间件

django项目默认有一些自带的中间件,如下:

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

​ 一般情况下这些中间件都会启用(最少CommonMiddleware会启用)

3. 自定义中间件说明

如果需要增加自定义的中间件(该中间件类必须继承MiddlewareMixin(django.utils.deprecation)),一般是添加在系统的中间件之下,如:

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',
    
    # 自定义中间件
    'my_app.middleware.MyMiddleware',
]

中间件中主要有以下方法(一个中间件类最少需要实现下列方法中的一个):

  • process_request:处理请求对象,请求到达django框架时,第一时间调用

    多个中间件之间顺序调用

    参数:request

    返回:

    • response:调用当前中间件的process_response处理
    • None:调用下一个中间件的process_request处理
  • process_response:处理响应对象,视图函数返回response后,调用

    多个中间件之间倒序调用

    参数:request, response

    返回:

    • response:调用上一个中间件的process_response处理
  • process_view:视图预处理,在视图函数处理之前调用,即请求在urlconf当中匹配到对应的视图函数之后,先不调用视图函数,而是先调用此方法

    多个中间件之间顺序调用

    参数:request,view_func,view_args,view_kwargs

    ​ view_func:url路由匹配到的视图函数, 不是字符串,是函数对象

    ​ view_args:视图函数的可变参数

    ​ view_kwargs:视图函数的可变关键字参数

    返回:

    • response:调用最后一个中间件的process_response开始处理
    • None:调用下一个中间件的process_view处理
  • process_exception:在视图函数处理过程抛出异常时调用,中间件的方法(除了process_template_response)中抛出异常不会触发

    多个中间件之间倒序调用

    参数:request,exception

    ​ exception:是处理过程中抛出的异常对象

    返回:

    • response:之后的process_exception都不会触发,而是直接调用最后一个中间件的process_response处理
    • None:调用上一个中间件的process_exception处理
  • process_template_response:默认不执行,在视图函数完成操作后调用,除非视图函数返回的response中有render方法

    多个中间件之间倒序调用

    参数:request,response

    ​ response:不是HttpReponse,而是具有render方法的对象,譬如:SimpleTemplateResponse对象,在(django.template.response中)

    返回:

    • response:具有render方法的对象,继续调用上一个中间件的process_template_response处理,最后一个process_template_response处理完成后,会自动调用 response对象中的render方法,得到一个HttpResponse对象,进行返回,再调用process_response操作
    中间件方法的执行时有顺序的,process_request与process_view是按照顺序去执行的,而process_response、process_exception和process_template_response是反序的 :
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-io9SmTjf-1669197395429)(.\imgs\image-20210729085034649.png)]

​ 总结:用户请求 >> process_request >> urlconf路由匹配,找到对应的视图函数 >> process_view >> 视图函数 >> process_template_response(如果视图函数返回的response,有render方法,否则这一步不会执行) >> process_response >> 返回response到用户

​ 其中,在 视图函数 和 process_template_response 处理过程中,如果出现 exception ,那么就会倒序执行 中间件的process_exception

3.1 中间件调用顺序测试process_request

结果:
先执行中间件,按照settings配置顺序执行,然后再执行视图函数
urls

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('middleware_app.urls'))
]

views

from django.shortcuts import render,HttpResponse

# Create your views here.
def middleware_test(request):
    print('调用了视图函数')
    return HttpResponse('测试中间件成功')

中间件

from django.utils.deprecation import MiddlewareMixin
class FirstMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('FirstMiddleware process_request')

class SecondMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('SecondMiddleware process_request')

class ThirdMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('ThirdMiddleware process_request')

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

    # 自定义中间件
    'middleware_app.middlewaretest.FirstMiddleware',
    'middleware_app.middlewaretest.SecondMiddleware',
    'middleware_app.middlewaretest.ThirdMiddleware',

]

3.2 中间件调用顺序测试process_response

**结果: **
当request存在返回时。
返回顺序,先执行First然后,执行Second的request,然后渲染模板,最后从2执行到1的response。不执行3。
因为如果request存在返回时,执行完return就直接执行response,response又是倒叙执行,就跳过了3

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse
class FirstMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('FirstMiddleware process_request')
    def process_response(self,request,response):
        print('FirstMiddleware process_response')
        return response


class SecondMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('SecondMiddleware process_request')
        return HttpResponse('return SecondMiddleware')
    def process_response(self,request,response):
        print('SecondMiddleware process_response')
        return response


class ThirdMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('ThirdMiddleware process_request')
    def process_response(self,request,response):
        print('ThirdMiddleware process_response')
        return response

3.3 中间件调用顺序测试process_view

结果
如果不存在返回的话,就先执行request、view,再执行函数视图,再倒叙执行response。
如果存在返回的话,就先执行request,再顺序执行到存在返回的视图(不执行之后的),最后倒叙执行response。不执行函数视图了。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse
class FirstMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('FirstMiddleware process_request')
    def process_response(self,request,response):
        print('FirstMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('FirstMiddleware process_view')


class SecondMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('SecondMiddleware process_request')
        # return HttpResponse('return SecondMiddleware')
    def process_response(self,request,response):
        print('SecondMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('SecondMiddleware process_view')
        return HttpResponse('return SecondMiddleware')

class ThirdMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('ThirdMiddleware process_request')
    def process_response(self,request,response):
        print('ThirdMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('ThirdMiddleware process_view')

3.4 中间件调用顺序测试process_exception

结果
先将视图函数view中添加抛出异常代码。若exception无返回时,就按顺序执行request,顺序执行view。再倒叙执行exception。执行完views视图函数之后,倒叙执行response;
就按顺序执行request,顺序执行view。再倒叙执行exception,但是执行到返回函数就不会继续倒叙执行了。执行完views视图函数之后(但不会执行函数中raise抛出的异常),倒叙执行response。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse
class FirstMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('FirstMiddleware process_request')
    def process_response(self,request,response):
        print('FirstMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('FirstMiddleware process_view')
    def process_exception(self, request, exception):
        print('FirstMiddleware process_exception')


class SecondMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('SecondMiddleware process_request')
        # return HttpResponse('return SecondMiddleware')
    def process_response(self,request,response):
        print('SecondMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('SecondMiddleware process_view')
        # return HttpResponse('return SecondMiddleware')
    def process_exception(self, request, exception):
        print('SecondMiddleware process_exception')
        return HttpResponse('return SecondMiddleware')


class ThirdMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('ThirdMiddleware process_request')
    def process_response(self,request,response):
        print('ThirdMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('ThirdMiddleware process_view')
    def process_exception(self, request, exception):
        print('ThirdMiddleware process_exception')
from django.shortcuts import render,HttpResponse

# Create your views here.
def middleware_test(request):
    print('调用了视图函数')
    raise Exception('中间件抛出异常')
    return HttpResponse('测试中间件成功')

3.5 中间件测试调用顺序process_template_response

结果
若视图函数中不存在render,就不会触发process_template_response函数;若存在,在调用完视图后倒叙执行。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse
class FirstMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('FirstMiddleware process_request')
    def process_response(self,request,response):
        print('FirstMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('FirstMiddleware process_view')
    def process_exception(self, request, exception):
        print('FirstMiddleware process_exception')
    def process_template_response(self,request, response):
        print('FirstMiddleware process_template_response')
        return response


class SecondMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('SecondMiddleware process_request')
        # return HttpResponse('return SecondMiddleware')
    def process_response(self,request,response):
        print('SecondMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('SecondMiddleware process_view')
        # return HttpResponse('return SecondMiddleware')
    def process_exception(self, request, exception):
        print('SecondMiddleware process_exception')
        return HttpResponse('return SecondMiddleware')
    def process_template_response(self,request, response):
        print('SecondMiddleware process_template_response')
        return response


class ThirdMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print('ThirdMiddleware process_request')
    def process_response(self,request,response):
        print('ThirdMiddleware process_response')
        return response
    def process_view(self,request,view_func,view_args,view_kwargs):
        print('ThirdMiddleware process_view')
    def process_exception(self, request, exception):
        print('ThirdMiddleware process_exception')
    def process_template_response(self,request, response):
        print('ThirdMiddleware process_template_response')
        return response

4. 常见自定义中间件功能

总之,你如果有对全局request或response的操作需求,那么就可以使用中间件,譬如:

  1. IP过滤:对一些特定IP地址返回特定响应
  2. URL过滤:如果用户访问的是login视图,则通过;如果访问其他视图,需要检测是不是有session已经有了就通过,没有就返回login页面。这样就不用在多个视图函数上写装饰器login_required
  3. 内容压缩:response内容实现gzip的压缩,返回压缩后的内容给前端
  4. CDN:内容分发网络,实现缓存,如果缓存中有数据直接返回,没有找到缓存再去请求视图
  5. URL过滤:某个子应用升级暂停使用,某个特定的path路径下的请求,返回一个特定页面

5. 使用中间件——示例URL过滤

如何使用Django中间件实现访问某一些网页进行跳转到指定URL的地址(登录,系统维护)
url

from django.contrib import admin
from django.urls import path, include
from . import views
urlpatterns = [
    path('middleware_test/',views.middleware_test),
    path('middleware_url/', views.middleware_url)
]

views

from django.shortcuts import render,HttpResponse
from django.template.response import SimpleTemplateResponse
def middleware_url(request):
    return HttpResponse('系统功能升级中') # 不会访问到他

中间件views

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse
from django.shortcuts import render
class URLMiddleware(MiddlewareMixin):
    def process_request(self,request):
        if request.path.startswith('/middleware_url/'):
            return render(request,'middleware_app/middleware_url.html')

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    系统在升级啊啊啊
</body>
</html>

settings配置

MIDDLEWARE = [
    # 最先触发
    'middleware_app.middlewaretest.URLMiddleware',

    '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_app.middlewaretest.FirstMiddleware',
    'middleware_app.middlewaretest.SecondMiddleware',
    'middleware_app.middlewaretest.ThirdMiddleware',

]
;