Django: Middleware

Django Middleware

中间件介绍

Middleware,即中间件, Django 对接收的请求和响应进行处理过程中,通过中间件对接收到的请求和响应进行选择和过滤,从而得到相应的输入和输出,事实上即为 Django 的一个 hook 框架,在调用相应的服务端函数之前,hook 函数会先接收到请求或者是响应,进行相应的处理,这就是 Middleware。

其实对于用户浏览器发送的一个请求来说,Middleware 其实就是请求从客户端到达视图之间的若干层,并且在 Django 调用视图函数,返回一个响应的时候,这个响应反过来也会从视图开始经过若干个中间件返回到浏览器上,输出相应的值,这就是 Django Middleware 的 request/response 模式:

client explorer middleware_1 middleware_2 middleware_k views ... request process with request request process with request request process with request request call view function response process with response response process with response response process with response client explorer middleware_1 middleware_2 middleware_k views

这看起来很像是一个递归的调用过程,请求在递归栈最后的栈顶上最终成为被调用的相应的视图函数的 request 参数,并生成相应的 response。而 response 同样,作为返回值从栈顶开始随着递归返回逐层返回最终响应到客户端浏览器上。

Django 的中间件可以通过两种方式来实现,第一种是函数的方式,这个函数接收一个 get_response 的可调用函数,最后返回另外一个可调用的函数 middleware,这个函数接收一个 request 并返回一个 response对象:

def your_middleware(get_response):
    
    def middleware(request):
        
        # executing code for each request before views or later middleware to be called
        
        response = get_response(request)
        
        # executing code for each request/response afater the view is called
        
        return response
    
    return middleware

也可以通过定义一个实例可调用的类的方式:

class YourMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        return
    
    def __call__(self, request):
        
        # executing code for each request before views or later middleware to be called
        
        response = get_response(request)
        
        # executing code for each request/response afater the view is called
        
        return response

Note:当使用类的方式来定义一个中间件时,初始化函数除了 self 之外有且只能有 get_response 这一个参数,但是在 __init__ 中也可以进行一些全局变量的定义。同样,__init__ 和其他类一样,只调用一次,而对于 __call__ 来说,会对每次接收到的请求都调用一次。

并且,如果一个中间件在不调用 get_response 的情况下希望直接返回一个 response,那么后续的中间件以及最终的 views.py 将不会接收到任何请求,也不会有任何的响应,而是将会直接从这个中间件开始返回响应:

client explorer middleware_1 middleware_2 middleware_k views ... request process with request request No request passing process with request without calling get_response response process with response response client explorer middleware_1 middleware_2 middleware_k views

接下来,通过在 setting.py 中将这个中间件的完整路径添加到 MIDDLEWARE 这个列表变量里面,这个路径应该是一个完整的 Python 路径:

MIDDLEWARE_CLASSES = [
    'yourapp.views.yourmiddleware',
    # default middleware classes below
    # ...
]

这样就可以激活中间件了。

Other special methods for class-based middleware

下面几个函数同样可以在定义的中间件类中实现:

class simplemiddleware:
    # def __init__ ...
    # def __call__ ...
    
    def process_view(request, view_func, view_args, view_kwargs):
        
        # do something before view function called
        return # response or None
    
    def process_template_response(request, response):
        
        # do something after view function called
        return # response or None
    
    def process_exception(request, exception):
        
        # do something for exception handling
        return # response or None

process_view

process_view(request, view_func, view_args, view_kwargs)

Django 将会在对视图函数进行调用,即进行视图的相关处理之前调用这个函数。这个函数需要返回一个 None 或者是一个 HttpResponse。如果返回的值是 None,则 Django 会继续处理这个请求,执行其他的 process_view 中间件,直到最后的视图。如果当前的 process_view 中间件已经返回了一个 HttpResponse 对象,则 Django 将不会继续执行后续的其他中间件,直接调用响应的视图,然后对返回的响应对象调用相应的响应处理中间件,最终向客户端服务器返回一个结果。

process_exception

process_exception(request, exception)

Django 将在视图处理时,即视图函数的执行过程中出现错误时调用这个函数。同样,这个函数需要返回一个 None 或者是一个 HttpResponse 对象,并且在返回 HttpResponse 的时候,为这个响应对象调用相应的响应中间件,最终返回一个结果到客户端浏览器上

process_template_response

process_template_response(request, response)

Django 中的请求(request)都是一个 HttpRequest 的对象,而响应(response)则都是 TemplateResponse 对象,由视图函数返回或者是经过中间件相应处理之后返回。

Django 当且仅当在完成视图处理,即视图函数的执行结束之后调用这个函数,如果这个函数接收到的响应对象包含一个 render() 方法的话,那么这个响应就是一个 TemplateResponse。这个函数必须返回一个实现了 render 方法的响应对象,可以通过改变接收到的响应对象中的 template_name 字段,context_data 字段,或者是直接返回一个全新的 TemplateResponse。

Note:不需要显式对响应进行渲染,当所有中间件调用完毕之后,这个响应的渲染将会自动完成

流式响应处理

因为 StreamingHttpResponse 中并不包含 content 标签,因此当中间件对一个请求内容进行相应的处理时,不能直接假设每一个响应都包含有这个标签,因此需要针对响应类型的不同进行不同的处理:

if response.streaming:
    # 如果是流式响应,
    response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
    # 如果不是,调用相应的响应内容处理函数
    response.content = alter_content(response.content)

Note:通常流式响应的响应内容是非常庞大的(例如大型文件,或者是日志文件等),因此中间件应当将响应内容通过一个生成器包装起来,并且不进行任何的"消费"(生成器就是对应生产者模式中的生产者),当且仅当在传输时再通过这个生成器进行分段的数据传输。生成器的包装实现如下:

def wrap_streaming_content(content):
    for chunk in content:
        yield alter_content(chunk)

异常处理

对于一个中间件或者是视图抛出的异常,Django 将会在中间件与中间件或者是中间件与视图之间对这个异常进行转换,例如一个后续中间件抛出了一个 page not found 404 的异常,对于当前的中间件来说,这个异常是在内层(回顾中间件的多层结构)当中隐藏的,当前中间件将会收到的是一个包含 404 错误代码的 Http 响应:

middleware 1 middleware k middleware k+1 ... request raises exception: page not found 404 converts to error code Http response receive Http response with error code without any excpetion middleware 1 middleware k middleware k+1

异步

Django 中间件可以支持任何非异步请求和异步请求的组合,不过默认中间件只能进行同步请求的处理。为了让中间件具备异步请求的处理能力,需要在中间件类或者是函数中对两个标签进行设置:

  • sync_capable - boolean:中间件是否可以处理同步请求,默认为 True
  • async_capable - boolean:中间件是否可以处理异步请求,默认为 False

当中间件不能同时处理这两种请求时,Django 就需要对请求进行转换来对这个中间件进行适配,而这会对性能造成影响(performance penalty);但当这两个变量都设置为 True 的时候,Django 会直接将请求传递给这个中间件,不进行任何的转换。

在 django.utils.decorators 模块中提供了三种装饰器来应用上面的两个标签变量:

  • sync_only_middleware:即 sync_capable = True, async_capable = False
  • async_only_middleware:即 sync_capable = False, async_capable = True
  • sync_and_async_middleware:即 sync_capable = True, async_capable = True

通过使用 sync_and_async_middleware 这个装饰器,可以让我们的中间件可以同时处理异步和非异步的请求。这个时候,中间件可以通过 async.iscoroutinefunction 判断 get_response 对象是否是一个 coroutine function,来区分异步请求和非异步请求:

import asyncio
from django.utils.decorators import sync_and_async_middleware

@sync_and_async_middleware
def simple_middle(get_response):
    # One-time configuration and initialization goes here
    if asyncio.iscoroutinefunction(get_response):
        # 异步处理
        async def middleware(request):
            # Do something here
            response = await get_response(request)
            return reponse
    else:
        # 非异步
        def middleware(request):
            # Do something here
            resposne = get_response(request)
            return response
    return middleware

Note:如果在这个中间件中还包含了 process_view,process_template_response 以及 process_exception,也需要适配相应的装饰器来保证每一个函数都在相同的处理模式下

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值