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_funcs
被reversed
函式反轉了,多以在下面的 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
方法,大致邏輯如下:
- 定義一個 handler,這個 handler 就是分發請求和執行檢視函式的邏輯;
- 反向遍歷
settings.MIDDLEWARE
;- . 動態 import
MIDDLEWARE
中定義的函式(定義為 middleware_item); - .
handler = middleware_item(handler)
- . 動態 import
_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 的邏輯.