Django中间件

什么Django中间件

  • 请求来的时候需要先经过中间件才能到真正的Django后端
  • 响应走的时候也需要经过中间件才能发送出去
  • 通俗的讲 中间件相当于是Django的门户, 你进来时要经过它, 出去的时候也要经过它
  • 介于request与response处理之间的一道处理过程, 并且在全局上改变django的输入与输出

Django自带的中间件

  • Django自带的中间件有七个, 在 setting.py 配置文件中, 每个中间件其实就是一个类
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  # 处理session
    'django.middleware.common.CommonMiddleware',  # 处理路由匹配是否带斜杠
    'django.middleware.csrf.CsrfViewMiddleware',  # 跨站请求伪造处理
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

中间件的作用

  • 既然请求来和响应走都经过中间件, 那么我们就可以对所有的请求做一些预处理, 对所有返回的响应也可以做处理
  • 每一个中间件都有具体的功能

中间件的场景

  • 用户黑名单校验
  • 用户访问频率校验
  • 网站全局用户身份校验

中间件的主要方法介绍

process_request

​ 请求来的时候从上往下依次执行配置文件中注册了的中间件里面的process_request方法 如果没有则直接跳过,如果该方法自己返回了HttpResponse对象,那么请求不再继续往后直接返回相应的数据。

​ 如果该方法自己返回了HttpResponse对象,那么请求不再继续往后直接返回相应的数据

process_response

​ 响应走的时候会从下往上依次执行配置文件中注册了的中间件里面的process_response方法 如果没有则直接跳过
​ 如果该方法自己返回了HttpResponse对象,那么响应会替换成该HttpResponse对象数据,而不再是视图函数想要返回给客户端的数据

​ 如果process_request返回了HttpResponse对象 那么会从当前位置从下往上执行每一个process_response

process_view

路由匹配成功之后执行视图之前从上往下执行配置文件中注册了的中间件里面的process_view方法

process_template_response

​ 视图函数执行完毕之后返回的对象中含有render属性对应一个render方法,则会从下往上执行配置文件中注册了的中间件里面的process_template_response方法

process_exception

​ 视图函数执行过程中报错并在返回响应的时候会从下往上执行配置文件中注册了的中间件里面的process_exception

自定义中间件流程

中间件操作

先创建文件夹middleware,再创建一个py文件

from django.utils.deprecation import MiddlewareMixin


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

    def process_response(self, request, response):
        print('from MyMiddleware1 process_response')
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('view_func', view_func)  # 即将执行的视图函数名
        print('view_args', view_args)  # 传给视图函数的位置参数
        print('view_kwargs', view_kwargs)  # 传给视图函数的关键字参数
        print('from MyMiddleware1 process_view')
    def process_template_response(self,response):
        print('from MyMiddleware1 process_template_response')
    def process_exception(self,request,exception):
        print('from MyMiddleware2 process_exception')
        print(exception)  # 打印异常信息


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

    def process_response(self, request, response):
        print('from MyMiddleware2 process_response')
        return response
    def process_view(self, request, view_func, view_args, view_kwargs):
        print('view_func', view_func)
        print('view_args', view_args)
        print('view_kwargs', view_kwargs)
        print('from MyMiddleware2 process_view')
    def process_template_response(self,response):
        print('from MyMiddleware2 process_template_response')  # 特定条件才能触发
        return response
    def process_exception(self,request,exception):
        print('from MyMiddleware2 process_exception')
        print(exception)  # 打印异常信息
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.middleware.mymiddle.MyMiddleware1',
    'app01.middleware.mymiddle.MyMiddleware2'
]

基于中间件思想编写项目

分析setting.py可以看到MIDDLEWARE是把包导入的方式写成字符串去实现。如果我们也想实现这种方法需要用到一个模块importlib

import importlib

module_path = 'ccc.b'  # ccc是目录 b是py文件
# 注意: 该方法最小只能到py文件名(模块)
res = importlib.import_module(module_path)  # 等同于 from ccc import b
print(res)

目录结构

# 在一个包中有好几个模块
notify/  # 包
    ├── __init__.py
    ├── email.py
    ├── qq.py
    └── wechat.py
├── settings.py
└── start.py

notif

__init__.py

import settings
import importlib
def send_all(msg):
    # 1.循环获取配置文件中字符串信息
    for str_path in settings.NOTIFY_FUNC_LIST:  # 'notify.email.Email'
        # 2.切割路径信息
        module_path, class_str_name = str_path.rsplit('.', maxsplit=1)  # ['notify.email','Email']
        # 3.根据module_path导入模块文件
        module = importlib.import_module(module_path)
        # 4.利用反射获取模块文件中对应的类名
        class_name = getattr(module, class_str_name)  # Email  Msg  QQ
        # 5.实例化
        obj = class_name()
        # 6.调用发送消息的功能
        obj.send(msg)

email.py

class Email(object):
    def __init__(self):
        pass  # 模拟发送邮件需要准备好的操作

    def send(self, msg):
        print("邮箱提醒:%s" % msg)

qq.py

class QQ(object):
    def __init__(self):
        pass  # 模拟发送邮件需要准备好的操作

    def send(self, msg):
        print("QQ提醒:%s" % msg)

wechat.py

class WeChat(object):
    def __init__(self):
        pass  # 模拟发送邮件需要准备好的操作

    def send(self, msg):
        print("微信提醒:%s" % msg)

settings.py

NOTIFY_FUNC_LIST = [
    'notify.email.Email',
    'notify.qq.QQ',
    'notify.wechat.WeChat',
]

start.py

import notify
if __name__ == '__main__':
    notify.send_all('来自Kevin的评论')

总结

​ 利用了面向对象以及面向对象的多态性(鸭子类型),再通过定制配置文件并存放不同功能类的字符串路径, 使用导入包就是导入__init__.py文件的特性, 在包中定义一个方法, 方法中用importlib导入了需要的模块。拿到这个模板通过获取的字符串路径最后一个类的字符串进行反射,就实现了可以同时进行多个类中功能的集合。合理的运用这种思想,就可以只修改settings.py中的路径地址,并增添或者关闭某些功能。重要的是关闭只需要注释就可以了。

CSRF跨站请求伪造

在Django中配置文件settings.py中自带CSRF校验的中间件,默认是打开的

'django.middleware.csrf.CsrfViewMiddleware',

前端进行请求,没有携带csrf随机码,POST会请求失败(403)

基于from表单提交 CSRF 请求

<form action="" method="post">
    {% csrf_token %}
    
</form>

添加之后会在页面上生成一个隐藏的 input 标签, 里面的 name 对应 csrfmiddlewaretoken, value 对应一串随机码, 请求发送的时候就回去校验这个随机码,这样 POST 请求就成功了

基于ajax提交 CSRF 请求

方式一

在 data 中放入 csrf 随机字符串。首先需要在form表单中写上 {% csrf_token %}然后直接通过jQuery语法获取到页面中隐藏的input框内的namevalue,将其放入data

    $.ajax({
        url: '',
        type: 'post',
        data: {
            'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val()
        },
        success: function (arg) {

        }
    })

方式二

直接拿到 csrf_token 值进行渲染,需要引号(不需要在form表单中写csrf_token)

data:{'csrfmiddlewaretoken':'{{ csrf_token }}'}

方式三

headers:{'X-CSRFToken':'{{ csrf_token }}'}

CSRF 校验局部禁用与局部使用

  • @csrf_exempt : 全局启用CSRF校验的时候, 使用该装饰器可以使得局部不进行校验
  • @csrf_protect : 全局禁用CSRF校验的时候, 使用该装饰器可以使得局部仍然进行校验

针对FBV

from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_protect\@csrf_exempt
def login(request):
    return render(request,'login.html')

针对CBV

from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.utils.decorators import method_decorators

# @method_decorator(csrf_protect, name='post')  开启校验 可以
# @method_decorator(csrf_exempt, name='post') 关闭校验 不可以
class MySelfCsrfToken(View):
    # @method_decorator(csrf_protect)   开启校验 可以
     # @method_decorator(csrf_exempt) 关闭校验 可以
    def dispatch(self, request, *args, **kwargs):
        return super(MySelfCsrfToken, self).dispatch(request, *args, **kwargs)
   # @method_decorator(csrf_protect)  #  开启校验 可以
   # @method_decorator(csrf_exempt)  # 关闭校验 不可以
    def post(self, request):
        return HttpResponse('post 请求')

补充

  • 针对csrf_protect符合装饰器的三种玩法
  • 针对csrf_exempt只能给dispatch方法加才有效
Last modification:May 29, 2022
如果觉得我的文章对你有用,请随意赞赏