django框架–中间件系统

目录

零、参考

https://www.jb51.net/article/136422.htm
https://www.jb51.net/article/143832.htm
https://www.jb51.net/article/69953.htm

一、中间件的基本理解

我对django中间件的理解:以组件化的形式,为大量的请求或响应提供批量化处理的接口,封装着可插拔式的独立附加功能逻辑,与基本web业务逻辑功能解耦,通过hook函数能更细致的处理请求或响应过程。

django的中间件有如下特点:
1、每个中间件由一个类来表示
2、中间件的逻辑必须写在特定的接口中,这些接口被称为hook函数
3、中间件的执行有顺序依赖
4、hook函数的执行有规定顺序
5、中间件的启用会影响所有的请求/响应
6、中间件是可插拔式的,这意味着可以不启用任何中间件
7、中间件应该仅作为数据过滤器的角色对数据过滤、转换、清洗,对数据的业务处理应该放在视图系统中
8、如第7点,中间件应该作为额外功能模块介入请求/响应流程,与普通业务处理模块(视图系统)解耦

二、中间件的系统定位

中间件在django框架中的定位图

三、中间件的配置

配置中间件类

from django.utils.deprecation import MiddlewareMixin

class MyMiddleware(MiddlewareMixin):
    '''
    自定义类名,继承内置的中间件混合类。
    hook函数有固定的接口,自定义逻辑处理代码
    '''
    def process_request(self, request):
        pass
    
    def process_view(self, request, callback, callback_args, callback_kwargs):
        pass
        
    def process_exception(self, request, exception):
        pass
        
    def process_template_response(self, request, response):
        return response
    
    def process_response(self, request, response):
        return response

编写中间件hook函数逻辑

1、process_request(self, request)
参数requestHttpRequest对象,此hook函数将会在路由分发前执行,有两类返回值:

1. return None  # 请求流程将会继续按照原计划执行,这应该是默认设置
2. return HttpResponse  # 请求将会跳转到当前中间件的process_response函数处理并进入响应流程

注意:虽然return一个非None且非HttpResonse的值也会使得流程跳转到响应流程,不过并不建议这么做,因为每一个process_response函数都期望接收到一个HttpResponse对象以便做进一步的处理,而不是收到一个奇怪的字符串或者数字。

注意:进入响应流程的入口是当前中间件的process_response

2、process_view(self, request, callback, callback_args, callback_kwargs)
请求流程完成路由分发后,在执行视图函数前将会执行此hook函数。此函数的callback是对路由分发确定的视图函数的引用,callback_args, callback_kwargs是传递给视图函数的参数,有两类返回值:

1. return None  # 请求流程将会按照原计划继续,这应该是默认设置
2.return HttpResponse  # 请求将会跳转到最后一个中间件的process_response函数处理并进入响应流程

注意:进入响应流程的入口是最后一个中间件的process_response

3、process_template_response
view 视图函数中使用 render 渲染一个模版对象完成之后被调用,它必须返回一个render方法执行后的response对象。

4、process_exception(self, request, exception)
当视图函数执行出错的时候,会把错误抛给此hook函数,有两类返回值:

1. return None  # 将会把错误对象exception提交给前一个中间件的process_exception处理
2. return HttpResponse  # 将会跳转到最后一个中间件的process_response函数处理并进入响应流程

注意:不应该return exception

注意:进入响应流程的入口是最后一个中间件的process_response

5、process_response(self, request, response)
hook函数将在响应流程中执行,函数必须返回HttpResponse对象

return HttpResponse  # 把响应对象交给前一个中间件的process_response函数处理,如果已经是第一个中间件,将会交给wsgi服务器处理并发送给用户浏览器。

注意:必须返回HttpResponse对象

启用中间件

在项目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',
    'app01.my_middlewares.MyMiddleware',  # 添加对自定义中间件类的引用以启动
]

四、中间件的执行流程

中间件及hook函数执行流程(省略process_template_response)

五、中间件与装饰器之间的思考

中间件的功能划分遵循原则:视图函数仅完成本应完成的工作,额外的功能通过中间件来单独提供
中间件是可插拔式即意味着中间件的启用和禁用均不会影响视图函数的原始工作,这非常像之前学习过的python装饰器。python装饰器实现了设计模式中的装饰模式,装饰器的目的是:在保持原有函数功能的基础之上,新增额外的功能,且新增的功能应该与原函数功能解耦,装饰器也可以有选择的增加或者移除。通过自己的研究和网上各大神的博客学习中发现,django的中间件其实也是一种装饰模式,而且可以和python的装饰器用法高度适配,我用如下两张图来对django中间件和装饰器进行了转换。

图一、django中间件到装饰器的转换

图二、django中间件到装饰器的转换

python多重装饰器
虽然还没研究过django中间件的源代码,不过我想先尝试着使用python的装饰器来模拟中间件的效果。
首先,要理解装饰器的核心知识:利用闭包特性来保存内层函数的执行上下文。正因为闭包的存在,内层函数的执行上下文(执行环境)即使在外层函数结束后依然可以被保存,这就意味着在外层函数结束后,依然可以正确的执行内层函数。 (ps:如果不使用闭包,外层函数结束后,该函数中的所有变量都会被销毁)

其次,装饰器可以迭代使用

迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。
—百度百科

就像这样:

@IPFilter
@UserAuth
@DataTransform
@TrafficLog
def index(request):
    # somecode...
    return response

利用装饰器函数模拟中间件效果

现在我们通过一个多重函数装饰器简单的模拟一下中间件的效果,需求如下:

有一个ip黑名单列表,列表中的ip不能访问页面。此外,有三个函数需要定义:
一个简单的show_page函数,将会模拟用户访问某一个页面,并返回简单的内容(当前用户的ip)。
一个filter_ip装饰器,过滤恶意ip,如果用户ip在黑名单中就无法正常访问页面。
一个traffic_log装饰器,对正常访问的流量进行统计。
基础需求:通过自定义一个request对象模拟用户浏览器发出的http请求对象,request直接执行show_page视图函数以得到期望访问的http页面。
额外需求:通过添加以上两个装饰器来增加ip过滤和流量统计的功能。

代码定义如下:

# 黑名单的定义
black_ip_list = ['10.1.1.1', '172.16.1.1', '192.168.1.1']

# 这里简单的使用全局变量来表示统计流量
traffic_count = 0  


# request对象的定义
class Request(object):
    def __init__(self, source_ip):
        self.source_ip = source_ip


# filter_ip过滤器函数的定义
def filter_ip(func):
    def inner(request):
        source_ip = request.source_ip
        if source_ip in black_ip_list:
            response = '你的ip在黑名单中'
        else:
            response = func(request)
        return response
    
    return inner

# traffic_log流量统计函数的定义
def traffic_log(func):
    def inner(request):
        global traffic_count  
        traffic_count += 1
        print('当前页面被有效请求的次数是:', traffic_count)

        response = func(request)
        return response

    return inner

# show_page视图函数的定义
def show_page(request):
    source_ip = request.source_ip
    response = '模拟的目标页面内容,此用户的ip是-->' + source_ip

    return response

结果1,实现最基本的用户访问

结果2,实现ip黑名单过滤

结果3,实现有效流量统计

结果4,实现ip黑名单过滤+有效流量统计(特别注意顺序依赖)

利用装饰器类模拟中间件效果
虽然简单的模拟出了中间件的可插拔、功能解耦、批量请求处理等功能,但还做的不够好,我们可以基于上面的代码,再做一些必要的封装,代码如下:

class TrafficLogMiddleware(object):
    traffic_count = 0

    def __init__(self, func):
        self.func = func

    def __call__(self, request):
        self.traffic_count += 1
        print('当前页面被有效请求的次数是:', self.traffic_count)

        response = self.func(request)
        return response

class FilterIPMiddleware(object):
    black_ip_list = ['10.1.1.1', '172.16.1.1', '192.168.1.1']

    def __init__(self, func):
        self.func = func

    def __call__(self, request):
        source_ip = request.source_ip
        if source_ip in self.black_ip_list:
            response = '你的ip在黑名单中'
        else:
            response = self.func(request)

        return response


class Request(object):
    def __init__(self, source_ip):
        self.source_ip = source_ip


@FilterIPMiddleware
@TrafficLogMiddleware
def show_page(request):
    source_ip = request.source_ip
    response = '模拟的目标页面内容,此用户的ip是-->' + source_ip

    return response

感觉不像django的中间件接口?可以这样写:

class Middleware(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, request):
        response = self.process_request(request)
        if not response:
            response = self.func(request)
        response = self.process_response(request, response)

        return response

    def process_request(self, request):
        pass

    def process_response(self, request, response):
        return response


class TrafficLogMiddleware(Middleware):
    traffic_count = 0

    def process_request(self, request):
        self.traffic_count += 1
        print('当前页面被有效请求的次数是:', self.traffic_count)

    def process_response(self, request, response):
        return response


class FilterIPMiddleware(Middleware):
    black_ip_list = ['10.1.1.1', '172.16.1.1', '192.168.1.1']

    def process_request(self, request):
        source_ip = request.source_ip

        if source_ip in self.black_ip_list:
            response = '你的ip在黑名单中'
        else:
            response = None

        return response

    def process_response(self, request, response):
        return response


class Request(object):
    def __init__(self, source_ip):
        self.source_ip = source_ip


@FilterIPMiddleware
@TrafficLogMiddleware
def show_page(request):
    source_ip = request.source_ip
    response = '模拟的目标页面内容,此用户的ip是-->' + source_ip

    return response

执行结果如下:

六、中间件的应用场景

中间件的启用会影响所有的请求/响应—>适用于大量请求/响应的批量化处理场景
中间件相互之间功能解耦,顺序依赖—>适合可插拔式的业务场景
中间件可以介入请求/响应流程—>适用于需要更加细致化处理请求/响应流程的业务场景
1、流量统计
2、恶意ip过滤
3、用户区分
4、缓存CDN
5、URL过滤
6、数据预处理
……

七、内置中间件

django框架内置了7个中间件,用于提供基本的http请求和响应处理,内置中间件的基本学习可以参考:
https://www.jb51.net/article/69953.htm

八、总结

1、装饰器和中间件都实现了装饰模式,此模式的目的是为了在不修改原有模块的条件下新增功能代码,并可以提供可插拔的效果,同时新增代码和原有代码功能上解耦。
2、类比学习很重要,可以同时提升对两个同类知识的理解。
3、中间件的角色应该是数据清洗/过滤/转换器,不应该在中间件上处理业务逻辑,而只是处理数据约束,具体的业务逻辑应该放置在视图函数中,这也是它的本职工作。
4、不要滥用中间件,过多的中间件会增加请求/响应流程的环节数,发生错误的时候提升排错难度。中间件的使用应该依赖业务场景,在最合适的地方使用最合适的技术,才能发挥最高的效率。