Flask -- 请求流程源码解析
请求到来
项目启动后,当请求到来时,会执行 对象 + (), 执行__call__
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
跟踪到wsgi_app()
app.py
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
注:以下步骤主要是基于上面的源码延伸出的。
一. 首先执行 ctx = self.request_context(environ)
,创建RequestContext对象,封装 request和session。
environ:是请求的原始数据
def request_context(self, environ):
return RequestContext(self, environ)
# 相当于:ctx = RequestContext(environ) 实例化了一个对象
class RequestContext(object):
"""ctx对象中封装了 request和session"""
request = app.Request(environ)
self.request = request
self.session = session
二. 执行 ctx.push()
, 会将两个上下文对象添加到Local维护的字典中,是以 线程ID 为key, {‘stack’: []}} 为values。
2.1 首先会创建AppContext对象, 封装 app和g。
def push(self):
app_ctx = self.app.app_context()
def app_context(self):
return AppContext(self)
# 相当于app_ctx = AppContext()
class AppContext(object):
"""app_ctx对象封装了 app和g"""
self.app = app
self.g = app.app_ctx_globals_class()
2.2 执行app_ctx.push()
,将app_ctx 对象添加到Local创建的字典中 {线程ID:{‘stack’: [app_ctx,]}}
ps:详见LocalStack 和 Local 实现上下文管理
_app_ctx_stack = LocalStack()
_app_ctx_stack.push(self)
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self._local, "stack", None)
# 列表
if rv is None:
self._local.stack = rv = []
rv.append(obj) # 添加到栈中
return rv
2.3 执行_request_ctx_stack.push(self)
, 将ctx 对象添加到另一个Local的 字典中 {线程ID:{‘stack’: [ctx,]}}
_request_ctx_stack = LocalStack()
_request_ctx_stack.push(self)
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self._local, "stack", None)
if rv is None:
self._local.stack = rv = []
rv.append(obj) # 添加到栈中
return rv
三. 执行response = self.full_dispatch_request()
,主要是执行before/视图/after(处理session) .
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
3.1 首先执行self.try_trigger_before_first_request_functions()
,判断是否是第一次请求,然后循环执行列表中的before_first_request装饰的函数。
def try_trigger_before_first_request_functions(self):
if self._got_first_request:
return
with self._before_request_lock:
if self._got_first_request:
return
for func in self.before_first_request_funcs:
func() # 执行函数
self._got_first_request = True
3.2 执行rv = self.preprocess_request()
,主要是执行before_request装饰的函数, 这里使用了chain
函数,迭代循环 app以及蓝图中的before_request装饰的函数。
from itertools import chain
funcs = self.before_request_funcs.get(None, ())
if bp is not None and bp in self.before_request_funcs:
funcs = chain(funcs, self.before_request_funcs[bp])
for func in funcs:
rv = func() # 执行,rv接收返回值
if rv is not None:
# 如果有返回值,直接返回给用户,不再往下走
return rv
3.3 执行rv = self.dispatch_request()
, 主要是执行视图函数,
self.view_functions[rule.endpoint](**req.view_args)
# 通过view_functions={}中, endpoint与函数的对应关系去执行视图函数
3.4 执行self.finalize_request(rv)
, 执行after_request装饰的函数,并且j将session加密保存再cookie中。有返回值response。
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
try:
response = self.process_response(response)
# 执行
return response
def process_response(self, response):
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
# 获取after装饰的函数列表,进行反转
for handler in funcs:
# 循环执行
response = handler(response)
if not self.session_interface.is_null_session(ctx.session):
self.session_interface.save_session(self, ctx.session, response) # 保存session
return response
四. 执行ctx.auto_pop(error)
, 主要是将数据返回给用户后,销毁 ctx/app_ctx 对象,以防止内存泄漏。
def auto_pop(self, exc):
self.pop(exc)
def pop(self, exc=_sentinel):
rv = _request_ctx_stack.pop() # 将ctx对象删除
app_ctx.pop(exc) # 将app_ctx对象删除
总结:
-
创建ctx = RequestContext对象,其内部封装了 Request对象和session数据。
-
创建app_ctx = AppContext对象,其内部封装了App和g。
-
然后ctx.push触发将 ctx 和 app_ctx 分别通过自己的LocalStack对象将其放入到Local中,Local的本质是以线程ID为key,以{“stack”:[]}为value的字典。
{ 1111:{“stack”:[ctx,]} } { 1111:{“stack”:[app_ctx,]} }
注意:以后再想要获取 request/session / app / g时,都需要去local中获取。
-
执行所有的before_request函数
-
执行视图函数
-
执行所有after_request函数(session加密放到cookie中)
-
将数据返回给用户后,销毁ctx和app_ctx。