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_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 的逻辑.