zoukankan      html  css  js  c++  java
  • 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
    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 的逻辑.
  • 相关阅读:
    css问题
    前端性能优化整理
    算法之排序
    浅谈webpack优化
    js设计模式
    事件模型
    浏览器缓存
    ucGUI 12864 从打点起
    ucGUI例程收藏
    Qt 自动搜索串口号列表
  • 原文地址:https://www.cnblogs.com/aloe-n/p/13804703.html
Copyright © 2011-2022 走看看