Flask 和 Django 中间件对比

Flask 和 Django 中间件对比

假设我们的需求是在处理请求和返回响应前打印两次 log

Flask 中间件定义方式

@app.before_request
def br1():
    print('before_request 1')


@app.before_request
def br2():
    print('before_request 2')


@app.after_request
def ar1():
    print('after_request 1')


@app.after_request
def ar2():
    print('after_request 2')

@app.route('/')
def index():
    print('hello world')
    return 'hello world'

app.run()

启动服务并接受请求后,打印如下内容

before_request 1
before_request 2
hello world
after_request 2
after_request 1

Flask 中间件分析

可以看到 before_request 拦截器是顺序执行的,而 after_request 却是逆序的.

下面分析下原因:

before_request为例

class Flask:
    # ...
    def before_request(self, f):
        # 其实就是这样
        # a = {None:[]}
        # a[None].append(f)
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

程序启动时,所有的 before_request 装饰器都会执行,进而为 before_request_funcs 赋值,

- app.before_request_funcs: {None: [<function br1 at 0x11fded730>, <function br2 at 0x11fe30bf8>]}
- app.after_request_funcs: {None: [<function ar1 at 0x11fe30c80>, <function ar2 at 0x11fe30d08>]}

从上面的调试信息中可以看到,app.before_request_funcs 和 app.after_request_funcs 都是顺序的,那 after_request_funcs 执行时为什么是逆序的呢?

class Flask:
    # ...
    def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.save_session(ctx.session, response)
        return response

在上面的代码中,after_request_funcsreversed函数反转了,多以在下面的 for 循环中遍历的其实是逆序额的 after_request_funcs.

Django 中间件定义方式

{main_app}/middleware.py下定义如下一个装饰器函数.

def print_log1(handle):
    def inner(request):
        print("before_request 1")
        response = handle(request)
        print("after_request 1")
        return response

    return inner


def print_log2(get_response):
    def inner(handle):
        print("before_request 2")
        response = get_response(handle)
        print("after_request 2")
        return response

    return inner

然后在 setting.py 中 MIDDLEWARE 添加上

MIDDLEWARE = [
    # ...
    'djangotest.middleware.print_log1',
    'djangotest.middleware.print_log2'
]

定义视图函数

def index(request):
    print("hello world")
    return "hello world"

启动服务并接受请求后,打印如下内容

before_request 1
before_request 2
hello world
after_request 2
after_request 1

Django 中间件分析

可以看到请求进来时顺序和我们在 MIDDLEWARE 中定义的一致,返回时却刚好相反.

下面分析下原因.

/django/core/handlers/base.py中可以看出,response 是调用self._middleware_chain(request)返回的.

class BaseHandler
    # ...
    def get_response(self, request):
        """Return an HttpResponse object for the given HttpRequest."""
        # Setup default url resolver for this thread
        set_urlconf(settings.ROOT_URLCONF)
        response = self._middleware_chain(request)
        response._closable_objects.append(request)
        if response.status_code >= 400:
            log_response(
                '%s: %s', response.reason_phrase, request.path,
                response=response,
                request=request,
            )
        return response

剩下的就是看_middleware_chain逻辑了.

程序启动时会执行load_middleware方法,大致逻辑如下:

  1. 定义一个 handler,这个 handler 就是分发请求和执行视图函数的逻辑;
  2. 反向遍历settings.MIDDLEWARE;
    • . 动态 importMIDDLEWARE中定义的函数(定义为 middleware_item);
    • . handler = middleware_item(handler)
  3. _middleware_chain = handler
class BaseHandler:
    # ...
    def load_middleware(self):
        # ...

        handler = convert_exception_to_response(self._get_response)
        for middleware_path in reversed(settings.MIDDLEWARE):
            middleware = import_string(middleware_path)
            try:
                mw_instance = middleware(handler)
            except MiddlewareNotUsed as exc:
                if settings.DEBUG:
                    if str(exc):
                        logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                    else:
                        logger.debug('MiddlewareNotUsed: %r', middleware_path)
                continue

            if mw_instance is None:
                raise ImproperlyConfigured(
                    'Middleware factory %s returned None.' % middleware_path
                )

            if hasattr(mw_instance, 'process_view'):
                self._view_middleware.insert(0, mw_instance.process_view)
            if hasattr(mw_instance, 'process_template_response'):
                self._template_response_middleware.append(mw_instance.process_template_response)
            if hasattr(mw_instance, 'process_exception'):
                self._exception_middleware.append(mw_instance.process_exception)

            handler = convert_exception_to_response(mw_instance)

        self._middleware_chain = handler

所以其实 Django 的中间件是一层又一层的装饰器函数将原始的 handler 进行了层层包裹f(x) = f2(f1(f(x))),处理顺序自然也和普通装饰器一致,进入先执行的是最外层,返回时先执行最内层.

总结

同样是 WSGI 框架,Flask 和 Django 在中间件定义上差别还是很大的,Flask 使用装饰器需要将 before_request 和 after_request 分别定义,而 Django 则是定义一个装饰器函数. 这样的差别是因为它们对中间件的组织方式以及调用方式不同:

  • Flask 将 before_request_func / after_request_func 保存在一个列表中,在处理请求/返回响应前遍历列表对请求/响应进行处理;
  • Django 在程序启动时遍历 middleware 对原始 handler 进行层层包装,处理请求时的 handler 已经包括了 middleware 的逻辑.
posted @ 2020-10-12 19:20  Aloe_n  阅读(293)  评论(0编辑  收藏  举报