1. 程式人生 > 實用技巧 >Flask 和 Django 中介軟體對比

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 的邏輯.