第十三篇:Django之中间件

第十三篇:Django之中间件

一、前戏

1、什么是中间件?

中间件是一个用来处理Django的请求和响应的框架级别的钩子。

它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。

我们再次看一下django生命周期流程图。

img

django中间件是django的门户.
1.请求来的时候需要先经过中间件才能到达真正的django后端
2.响应走的时候最后也需要经过中间件才能发送出去

django中间件能够做的事情(只要是涉及到项目全局的功能,你一定要想到中间件)
(每个中间件其实就类似于一块独立的功能)
1.全局身份校验
2.全局访问频率校验
3.全局权限校验

2、有哪些中间件?

我们一直都在使用django的中间件,打开settings.py文件,我们可以看到djano自带的7个中间件。

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

二、django中间件方法

1、django中间件源码

我们打开django中间件的源码,进行观察。

打开SessionMiddleware的源码,我们发现。

打开CsrfViewMiddleware源码。

打开AuthenticationMiddleware源码。

到了这里,我们发现django中间件总有几个特定的方法。django支持程序员自定义中间件并且暴露给程序员五个可以自定义的方法。分别是

# 掌握
process_request

process_response
# 了解
process_view

process_template_response

process_exception

2、如何自定义django中间件?

1.在项目名或者应用名下创建一个任意名称的文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内需要书写类(这个类必须继承MiddlewareMixin),然后在这个类里面就可以自定义五个方法了
  (这五个方法并不是全部都需要书写,用几个写几个)
4.需要将类的路径以字符串的形式注册到配置文件中才能生效
    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',
        '你自己写的中间件的路径2',
        '你自己写的中间件的路径3',
    ]

如图所示。

  • process_request
"""我们先mymiddle.py定义两个中间键"""
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MyMiddleWare1(MiddlewareMixin):
    # 必须有一个request参数,携带请求的内容,所以在中间件中可以先视图层进行操作
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
        

class MyMiddleWare2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个自定义中间件里面的process_request方法')
        

"""注册"""
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',
    # 注册自己的中间件(在应用下创建路径有提示,但是如果在项目下创建就没有提示了 你就需要自己比对着书写)
    'app01.mymiddleware.mymiddle.MyMiddleWare1',
    'app01.mymiddleware.mymiddle.MyMiddleWare2'
]


"""同时我们建立视图函数,为了方便测试"""
def index(request):
    print('我是视图层中的index打印的信息')
    return HttpResponse('data from index')

运行django项目,我们访问127.0.0.1:8000/index/,我们可以在后端看见如此结果。

接下来,我们给中间件MyMiddleWare1进行修改,我们会发现。

class MyMiddleWare1(MiddlewareMixin):
    # 必须有一个request参数,携带请求的内容,所以在中间件中可以先视图层进行操作
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
        return HttpResponse('我被第一个自定义中间件中的process_request方法截胡了')

再次访问页面,效果如下。

"""我们可以得出如下结论"""
1.请求来的时候需要经过每一个中间件里面的process_request方法,
结果的顺序是按照配置文件中注册的中间件从前往后的顺序依次执行
2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
  而是直接原路返回(校验失败不允许访问...)
  process_request方法就是用来做全局相关的所有限制功能(类似于403forbidden)
  • process_response

我们将中间件中的类进行修改,进行测试。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class MyMiddleWare1(MiddlewareMixin):
    # 必须有一个request参数,携带请求的内容,所以在中间件中可以先视图层进行操作
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第一个自定义中间件里面的process_response方法')
        # 必须返回response,就是django后端返回给浏览器的内容【也可以在这里被修改】
        return response


class MyMiddleWare2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第二个自定义中间件里面的process_response方法')
        return response

我们可以得出这样的效果。

从这种效果中,我们可以推导出什么结论呢???

我们发现,django中间件的执行顺序是下面这样的。

我们可以在process_response中对视图层传来的数据进行一个修改【太强了】,代码如下。

class MyMiddleWare1(MiddlewareMixin):
    # 必须有一个request参数,携带请求的内容,所以在中间件中可以先视图层进行操作
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第一个自定义中间件里面的process_response方法')
        # # 第一次先将后端传过来的数据修改
        return HttpResponse('data from MyMiddleWare1')


class MyMiddleWare2(MiddlewareMixin):
    # 必须有一个request参数,携带请求的内容,所以在中间件中可以先视图层进行操作
    def process_request(self, request):
        print('我是第二个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第二个自定义中间件里面的process_response方法')
        # 第一次先将后端传过来的数据修改
        return HttpResponse('data from MyMiddleWare2')

最终效果如下。

还有一个问题,研究如果在第一个process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况?

"""主要看MyMiddleWare1"""
class MyMiddleWare1(MiddlewareMixin):
    # 必须有一个request参数,携带请求的内容,所以在中间件中可以先视图层进行操作
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
        return HttpResponse('我被第一个自定义中间件中的process_request方法截胡了')
    def process_response(self, request, response):
        print('我是第一个自定义中间件里面的process_response方法')
        # 必须返回response,就是django后端返回给浏览器的内容【也可以在这里被修改】
        return HttpResponse('data from MyMiddleWare1')


class MyMiddleWare2(MiddlewareMixin):
    # 必须有一个request参数,携带请求的内容,所以在中间件中可以先视图层进行操作
    def process_request(self, request):
        print('我是第二个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第二个自定义中间件里面的process_response方法')
        return HttpResponse('data from MyMiddleWare2')

后端效果如下。

我们发现直接从同级别开始返回,返回给前端的数据时process_response中的数据。【flask框架也有一个中间件,但是它的规律不同,只要返回数据了就必须经过所有中间件里面的类似于process_reponse方法】

"""我们可以得出如下结论"""
1.响应走的时候需要结果每一个中间件里面的process_response方法
  该方法有两个额外的参数request,response
2.该方法必须返回一个HttpResponse对象,默认返回的就是形参response,你也可以自己返回自己的
3.顺序是按照配置文件中注册了的中间件从后往前依次经过,如果你没有定义的话,直接跳过执行下一个
  • process_view

    我们进行测试,代码如下。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MyMiddleWare1(MiddlewareMixin):
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第一个自定义中间件里面的process_response方法')
        return response
    # 我们根据源码,发现这样写参数
    def process_view(self, request, view_name, *args, **kwargs):
        print(view_name, args, kwargs)
        print('我是第一个自定义中间件里面的process_view方法')

class MyMiddleWare2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第二个自定义中间件里面的process_response方法')
        return response
    def process_view(self, request, view_name, *args, **kwargs):
        print(view_name, args, kwargs)
        print('我是第二个自定义中间件里面的process_view方法')

得到如下效果。

"""我们可以得出结论"""
1.路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该放法
2.顺序是按照配置文件中注册的中间件从前往后的顺序依次执行
  • process_template_response

测试代码如下。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MyMiddleWare1(MiddlewareMixin):
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第一个自定义中间件里面的process_response方法')
        return response
    def process_template_response(self,request,response):
        print('我是第一个自定义中间件里面的process_template_response')
        return response

class MyMiddleWare2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第二个自定义中间件里面的process_response方法')
        return response
    def process_template_response(self,request,response):
        print('我是第二个自定义中间件里面的process_template_response')
        return response
    
    
"""views.py"""
def index(request):
    print('我是视图层中的index打印的信息')
    obj = HttpResponse('data from index')
    def render():
        print('内部的render函数执行')
        return HttpResponse('内部的render函数执行~~~')
    obj.render = render
    return obj

执行结果如下。

"""我们可以得出结论"""
返回的HttpResponse对象有render属性的时候才会触发
顺序是按照配置文件中注册了的中间件从后往前依次经过
  • process_exception
# 书写方式如下
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MyMiddleWare1(MiddlewareMixin):
    def process_request(self, request):
        print('我是第一个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第一个自定义中间件里面的process_response方法')
        return response
    def process_exception(self, request, exception):
        print('我是第一个中间件里面的process_exception')
        print(exception)  # 打印的是报错信息

class MyMiddleWare2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个自定义中间件里面的process_request方法')
    def process_response(self, request, response):
        print('我是第二个自定义中间件里面的process_response方法')
        return response
    def process_exception(self, request, exception):
        print('我是第二个中间件里面的process_exception')
        print(exception)

"""我们可以得出结论"""
当后端视图函数出现报错的时候执行
exception为报错信息提示

三、csrf跨站请求伪造

1、什么是csrf跨站请求伪造?

谈到这里,就不得不提钓鱼网站了,哈哈。

假如说,我搭建一个跟正规网站一模一样的界面(银行转账页面),用户进入我们的网站,给某个人打钱,打钱的操作确确实实是提交给了银行系统上,用户的钱也确确实实减少了,但是唯一不同的时候打钱的账户不适用户想要打的账户变成了一个莫名其妙的账户。

这是怎么实现的呢?

# 内部本质
我们在钓鱼网站的页面,针对对方账户,只给用户提供一个没有name属性的普通input框
而我们在内部隐藏一个已经写好name和value的input

我们简单做一个做一个钓鱼网站,来阐释这个问题。

正经网站代码。

"""urls.py"""
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 正经转账网站
    url(r'^transfer/', views.transfer)
]

"""views.py"""
# 正经转账【没有使用数据库,简便演示】
def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        transfer_user = request.POST.get('transfer_user')
        transfer_money = request.POST.get('transfer_money')
        print(f'{username}{transfer_user}转了{transfer_money}元...')
    return render(request, 'transfer.html')

"""transfer.html"""
<h1 class="text-center">正经银行系统页面</h1>
<form action="" method="post">
    <p>username:<input type="text" name="username" class="form-control"></p>
    <p>transfer_user:<input type="text" name="transfer_user" class="form-control"></p>
    <p>transfer_money:<input type="text" name="transfer_money" class="form-control"></p>
    <input type="submit" class="btn btn-primary btn-block">
</form>

现在我们在写一个钓鱼网站。

"""钓鱼转账页面"""
<h1 class="text-center">钓鱼银行系统页面</h1>
{#网站地址写全称#}
<form action="http://127.0.0.1:8000/transfer/" method="post">
    <p>username:<input type="text" name="username" class="form-control"></p>s
    <p>transfer_user:<input type="text" class="form-control"></p>
{#我们只需要钓鱼的input隐藏即可#}
    <input type="text" name="transfer_user" value="yangyi" style="display: none">
    <p>transfer_money:<input type="text" name="transfer_money" class="form-control"></p>
    <input type="submit" class="btn btn-danger btn-block">
</form>
    
    
"""views.py"""
def transfer(request):
    return render(request, 'transfer.html')

好了,到现在我们测试以下效果。

如果打开正经的银行系统网站。

但是如果不小心,进入到钓鱼网站页面,我们再次进行输入。

【雷超本人觉得他确实转了,安安却没有收到,但是钱却莫名其妙到了别人的账户上,这便是钓鱼网站。】

2、如何使用csrf校验?

如何规避上述问题,使用csrf跨站请求伪造校验,用户访问提交数据的网站时,网站在给用户返回一个具有提交数据功能页面的时候,会给这个页面加一个唯一标识。

当这个页面朝后端发送post请求的时候,我的后端会先校验唯一标识,如果唯一标识不对,直接拒绝(403 forbbiden),如果成功则正常执行。

我们朝后端提交post数据,只有两种方式,form表单和ajax,我们单独进行讨论。

  • form表单如何符合校验【终于不用注释csrf了,哈哈】
"""
其实很简单,就是后端发送页面的时候,顺带给这个页面加入了一个input标签
提交post请求的时候只需要将该input的name和value传入即可。
"""

<form action="" method="post">
    # form表单只需要加一个{% csrf_token %}即可。内部的input框
    {% csrf_token %}
    <p>username:<input type="text" name="username" class="form-control"></p>
    <p>transfer_user:<input type="text" name="transfer_user" class="form-control"></p>
    <p>transfer_money:<input type="text" name="transfer_money" class="form-control"></p>
    <input type="submit" class="btn btn-primary btn-block">
</form>
  • ajax如何符合校验
<button class="btn btn-warning btn-block" id="d1">ajax的post请求</button>

$('#d1').click(function () {
    $.ajax({
        url:'',
        type:'post',
        data:{
            'username':'yangyi',
        },
        success:function () {
        }
    })
})

我们点击按钮,发现ajax请求也是被拒绝。

"""第一种方法"""
# 第一种 利用标签查找获取页面上的随机字符串
$('#d1').click(function () {
    $.ajax({
        url:'',
        type:'post',
        data:{
            'username':'yangyi',
            'csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()
        },
        success:function () {
        }
    })
})


# 第二种 利用模版语法提供的快捷书写
$('#d1').click(function () {
    $.ajax({
        url:'',
        type:'post',
        data:{
            'username':'yangyi',
            'csrfmiddlewaretoken': '{{ csrf_token }}'
        },
        success:function () {
        }
    })
})


# 第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可
{% load static %}
<script src="{% static 'js/mysetup.js' %}"></script>

"""mysetup.js中的代码【官方推荐】"""
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');


function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

四、csrf相关装饰器

现在我们想要实现下面的需求,那么该如何实现呢?

1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验

我们该如何试实现呢?这里就需要用到两个装饰器。

# 就像添加装饰器一样,直接使用即可。
csrf_protect  需要校验
csrf_exempt   忽视校验

"""FBV"""
from django.views.decorators.csrf import csrf_protect,csrf_exempt

@csrf_exempt
def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        transfer_user = request.POST.get('transfer_user')
        transfer_money = request.POST.get('transfer_money')
        print(f'{username}{transfer_user}转了{transfer_money}元...')
    return render(request, 'transfer.html')
"""CBV"""
csrf_protect  需要校验
    针对csrf_protect符合我们之前所学的装饰器的三种玩法
csrf_exempt   忽视校验
    针对csrf_exempt只能给dispatch方法加才有效
    
    
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator

from django.views import View

# @method_decorator(csrf_protect,name='post')  # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):
    # @method_decorator(csrf_protect)  # 针对csrf_protect 第三种方式可以
    # @method_decorator(csrf_exempt)  # 针对csrf_exempt 第三种方式可以
    def dispatch(self, request, *args, **kwargs):
        return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)

    def get(self,request):
        return HttpResponse('get')

    # @method_decorator(csrf_protect)  # 针对csrf_protect 第一种方式可以
    # @method_decorator(csrf_exempt)  # 针对csrf_exempt 第一种方式不可以
    def post(self,request):
        return HttpResponse('post')

五、基于django中间件的编程思想

1、importlib模块补充

要了解django中间件的编程思想,先要了解一个importlib模块,我们先建立相关文件夹。

"""正常导入"""
from myfile import b

print(b)  # <module 'myfile.b' from 'E:\\djangoProject\\day70_2\\myfile\\b.py'>
print(b.name)  # yangyi


"""importlib模块"""
import importlib

res = 'myfile.b'
ret = importlib.import_module(res)  # 等价于 from myfile import b 【缺陷:该方法最小只能到py文件名】

print(ret)  # <module 'myfile.b' from 'E:\\djangoProject\\day70_2\\myfile\\b.py'>
print(ret.name)  # yangyi

2、编程思想

我们有没有好奇过,django的中间件导入方式时以字符串的形式书写的,而且进行注销之后,就可以直接取消对应的功能,那样是如何实现的呢?

下面我们来进行揭秘。

我们先建立一个settings.py文件,再建立一个notify文件夹,文件路径如图。

代码如下。

"""settings.py"""
NOTIFY_LIST = [
    'notify.email.Email',
    'notify.qq.QQ',
    'notify.wechat.WeChat',
]


"""__init__()"""
import importlib
import settings

def send_all(content):
    for path_str in settings.NOTIFY_LIST:  # 'notify.email.Email'
        module_path, class_name = path_str.rsplit('.', maxsplit=1)
        # 1、利用字符串导入模块
        module = importlib.import_module(module_path)
        # 2、利用反射获取类名
        cls_name = getattr(module, class_name)  # 拿到类名Email、QQ、WeChat
        # 3 生成类的对象
        obj = cls_name()
        # 4、利用鸭子类型直接调用send方法
        obj.send(content)
        
        
"""qq.py"""
class QQ(object):
    def __init__(self): # 存放启动调用类的相关接口
        pass
    def send(self, content):
        print('短信通知:{}'.format(content))
        

"""wechat.py"""
class WeChat(object):
    def __init__(self):
        pass
    def send(self, content):
        print('微信通知:{}'.format(content))
        

"""email.py"""
class Email(object):
    def __init__(self):
        pass

    def send(self, content):
        print('邮件通知:{}'.format(content))
        
        
"""start.py"""
import notify

notify.send_all('曾经沧海难为水,除却巫山不是云。')

我们调用启动文件,即可发现。

如果我们随便注销一个功能,该功能便会失效,即实现了django中间件的"注销即失效"的效果。实现功能的插拔式设计。

posted @   YangYi215  阅读(323)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示