flask源码之上下文
引言:
上一篇说的是flask实例调用run方法的背后,其实刚开个头,flask的上下文才是整个flask的精华,所有的请求解析,视图函数的执行,以及响应处理等核心代码都聚集在这短短30行代码里。另外flask上下文分为请求上下文和程序上下文,实现的方法基本一致
代码:
上下文的入口,就是flask的__call__,至于为什么在上文中已经详细阐述,忘了就回去自己看看
1.0==== flask.__call__代码:
其实这个方法没什么,更像是一个统一的入口就调用了wsgi_app
def __call__(self, environ, start_response): """The WSGI server calls the Flask application object as the WSGI application. This calls :meth:`wsgi_app` which can be wrapped to applying middleware.""" # import traceback # s=traceback.extract_stack() # print("s===>%s"%s) # print("调用链 ======>") return self.wsgi_app(environ, start_response)
1.0.1==== wsgi_app代码:
看似平常,里边封装了n多的东西
def wsgi_app(self, environ, start_response): flask 核心源码 对于每次请求上下文的创建以及销毁也是在其内部完成的 所以上下文是flask框架的核心 所以上下文的主要功能和流程 1 对原生的请求进行封装生成视图函数可以操作的request对象 2 获取请求的cookie信息 生成session对象 3 执行预处理函数和视图函数 4 返回相应 """ # 生成ctx.request request.session请求上下文,即请求相关数据都封装到ctx对象中 # ctx.app 当前 app 名称 其实就是 flask 实例 # ctx.reqeust 请求相关 request对象 # ctx.session 为空 # ctx = self.request_context(environ) 在调该方法的时候实际上是 RequestContext 对象 # 因为 RequestContext 在实例化的时候 将self也就是flask对象当作参数传进去了所以 RequestContext # 的初始化方法中的app就是flask对象所以能ctx.app等 # print("wsgi_app===>environ--",environ) ctx = self.request_context(environ) error = None try: try: # ctx入栈 因为ctx是封装了请求相关 内部也将于应用上下文入栈 # 将localstack 添加到local中 并且这时候ctx 中的session不为None request_context中的request_class 给了个{} # 这个时候 app实例中已经包含了session的空字典 那么就可以在视图函数中 操作 ctx.push() # 对请求的url进行视图函数的匹配 执行视图函数 返回相应 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 # 出栈 删除本次请求的相关信息 也就是说 将自己请求的所有数据在local中删除调 ctx.auto_pop(error)
首先第一步调用了 self.request_context(environ)
1.0.2==== request_context代码:
也没什么就是 实例化了 RequestContext的类
def request_context(self, environ):
# print("====self",self)
# self 就是flask对象也就是app,RequestContext实例化的时候的app就是这个self
return RequestContext(self, environ)
1.0.3 ==== RequestContext.__init__代码:
这里request刚进来的时候,肯定是个None,但是紧接着 request = app.request_class(environ) 这行代码就比较有意思了
def __init__(self, app, environ, request=None, session=None): #print("in RequestContext ====>",app) self.app = app if request is None: # 这里实例化的实际上是 Request 实例 该类又继承 BaseRequest # 所以 这个requst可以在视图函数中 # 直接导入使用并且使用 request.forms 或request.args 这样的方法 request = app.request_class(environ) self.request = request self.url_adapter = None try: self.url_adapter = app.create_url_adapter(self.request) except HTTPException as e: self.request.routing_exception = e self.flashes = None self.session = session self._after_request_functions = []
1.0.3.1====app.request_class代码:
这里app明显指的就是flask实例,那么在flask中找到 request_class = Request ,那么根据这句 就知道 request = app.request_class(environ) 就知道 这里其实是实例的Request,那么下边要去看 Request的源码,但明显没有实现__init__方法 同样的套路去找长辈类,但是这里有意思的是pycharm直接跳到了另外一个叫Request的类
翻翻上边的引入
from werkzeug.wrappers import Request as RequestBase 所以这里其实是werkzeug.warpers里边的Requst类
class Request(RequestBase, JSONMixin):
1.0.3.2====Request as RequestBase 代码:
进去依然平淡无奇,就是继承了好多类 一个一个点进去看 其中在 BaseRequest 类中发现了很多熟悉的方法
class Request( BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthorizationMixin, CommonRequestDescriptorsMixin, ):
1.0.3.3=====BaseRequest.__init__代码:
平淡无奇就是 赋了值 self.environ["werkzeug.request"] = self 这个属性貌似要在后边用到
class BaseRequest(object): charset = "utf-8" encoding_errors = "replace" max_content_length = None parameter_storage_class = ImmutableMultiDict list_storage_class = ImmutableList dict_storage_class = ImmutableTypeConversionDict trusted_hosts = None disable_data_descriptor = False def __init__(self, environ, populate_request=True, shallow=False): self.environ = environ if populate_request and not shallow: self.environ["werkzeug.request"] = self self.shallow = shallow
发现的新大陆:
这不就是在视图函数中 request.form,request.values,request.args嘛,其他的常用方法都在这里封装,然后打印看看 wsgi_app中的environ,哎我去,尼玛
@cached_property def args(self): return url_decode( wsgi_get_bytes(self.environ.get("QUERY_STRING", "")), self.url_charset, errors=self.encoding_errors, cls=self.parameter_storage_class, ) @cached_property def form(self): self._load_form_data() return self.form @cached_property def values(self): args = [] for d in self.args, self.form: if not isinstance(d, MultiDict): d = MultiDict(d) args.append(d) return CombinedMultiDict(args)
返回wsgi_app:
代码紧接着 执行ctx.push 其实执行的还是 RequestContext 的push方法
def wsgi_app(self, environ, start_response):
# ctx = self.request_context(environ) 在调该方法的时候实际上是 RequestContext 对象
# 因为 RequestContext 在实例化的时候 将self也就是flask对象当作参数传进去了所以 RequestContext
# 的初始化方法中的app就是flask对象所以能ctx.app等
# print("wsgi_app===>environ--",environ)
ctx = self.request_context(environ)
error = None
try:
try:
# ctx入栈 因为ctx是封装了请求相关 内部也将于应用上下文入栈
# 将localstack 添加到local中 并且这时候ctx 中的session不为None request_context中的request_class 给了个{}
# 这个时候 app实例中已经包含了session的空字典 那么就可以在视图函数中 操作
ctx.push()
# 对请求的url进行视图函数的匹配 执行视图函数 返回相应
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
# 出栈 删除本次请求的相关信息 也就是说 将自己请求的所有数据在local中删除调
ctx.auto_pop(error)
1.1.0=====RequestContext.push
这里最核心的是这一句 _request_ctx_stack.push(self) 那么 _request_ctx_stack又是个什么玩意 查看导入 from .globals import _request_ctx_stack 来源flask 全局变量 类似thread.local的东西 具体原理去复习另外一篇 flask源码之全局变量
注意传值,将self传了进去,这个self就是RequestContext的实例,同时包含了 app ,session ........等
def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
# Before we push the request context we have to ensure that there
# is an application context.
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
# 程序上下文 和 请求上下文 基本一致
# app_ctx 这里其实就是 AppContext 的实例对象
# app_ctx.app 当前app对象
# app_ctx.g 只对当前app起作用的全局变量 用于保存一个周期中要存储的值
app_ctx = self.app.app_context()
#
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
if hasattr(sys, "exc_clear"):
sys.exc_clear()
# self 是 requestContext 对象 那个 其中包含了请求相关的数据
# _request_ctx_stack 就是globals中的 LocalStack() 对象
# 所以 _request_ctx_stack.push(self) 将请求相关的数据 全部添加到 Local 中
_request_ctx_stack.push(self)
# 获取session 所以就算请求刚进来 没有session 那么在这里赋值之后session 就不为空
if self.session is None:
# self.app.session_interface 这应该是个独立的类 专门处理session的接口
# session_interface=SecureCookieSessionInterface
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
self.session = session_interface.make_null_session(self.app)
if self.url_adapter is not None:
self.match_request()
1.1.1=== _request_ctx_stack.push
_request_ctx_stack = LocalStack() 所以_request_ctx_stack.push 调用的是 LocalStack.push
_request_ctx_stack = LocalStack()
1.1.2=== LocalStack.push
LocalStack是 伴随着 服务启动开始就被实例化的类,这里实例化就实例化了 Local() ,这里Local 什么都没做 就是实例化了2个属性,但是在赋值的时候并没有使用 self[key]=value这种方式,这是因为这个家伙实现了一堆双下方法,同时实现了__setitem__方法,使用这种方法赋值避免 死递归
同时要注意下方标红的代码
class LocalStack(object):
def __init__(self):
self._local = Local()
def push(self, obj):
# 将 loaclstack
rv = getattr(self._local, "stack", None)
# print("_local====>", self._local)
# print("rv====>",rv)
# 这里的rv是 None
# 将 loaclstack 添加到local中去 self._local = Local()
if rv is None:
# 这里会执行 Local() 的__setattr__方法 以后在用的时候小心死递归
# 这里就等于 local() 里的这个 storage[携程号][stack]=[]
self._local.stack = rv = []
# 这里的obj 就是 外边传进来的self 也就是 requestContext 对象
rv.append(obj)
return rv
Local代码:
class Local(object): __slots__ = ("__storage__", "__ident_func__") def __init__(self): object.__setattr__(self, "__storage__", {}) object.__setattr__(self, "__ident_func__", get_ident)
接着返回 wsgi_app 开始匹配执行视图函数
def wsgi_app(self, environ, start_response):
# ctx = self.request_context(environ) 在调该方法的时候实际上是 RequestContext 对象
# 因为 RequestContext 在实例化的时候 将self也就是flask对象当作参数传进去了所以 RequestContext
# 的初始化方法中的app就是flask对象所以能ctx.app等
# print("wsgi_app===>environ--",environ)
ctx = self.request_context(environ)
error = None
try:
try:
# ctx入栈 因为ctx是封装了请求相关 内部也将于应用上下文入栈
# 将localstack 添加到local中 并且这时候ctx 中的session不为None request_context中的request_class 给了个{}
# 这个时候 app实例中已经包含了session的空字典 那么就可以在视图函数中 操作
ctx.push()
# 对请求的url进行视图函数的匹配 执行视图函数 返回相应
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
# 出栈 删除本次请求的相关信息 也就是说 将自己请求的所有数据在local中删除调
ctx.auto_pop(error)
1.2.0=== full_dispatch_request 代码:
真正执行的是 dispatch_request 方法
def full_dispatch_request(self): # 执行before_first_request 只执行一次 中间件 self.try_trigger_before_first_request_functions() try: # 触发信号 request_startd信号 request_started.send(self) # 预处理 找到其中间件 看源码 貌似 调的app的蓝图 rv = self.preprocess_request() if rv is None: # 执行views rv = self.dispatch_request() except Exception as e: rv = self.handle_user_exception(e) # 处理 视图 函数 完成 之后 return self.finalize_request(rv)
1.2.1===dispatch_request 代码:
看到这如果记不清楚,去复习下 flask的路由,flask源码之路由
def dispatch_request(self): req = _request_ctx_stack.top.request if req.routing_exception is not None: self.raise_routing_exception(req) rule = req.url_rule # if we provide automatic options for this URL and the # request came with the OPTIONS method, reply automatically if ( getattr(rule, "provide_automatic_options", False) and req.method == "OPTIONS" ): return self.make_default_options_response() # otherwise dispatch to the handler for that endpoint # 还是 从self.view_functions 通过 endpoint 调的 return self.view_functions[rule.endpoint](**req.view_args)
这里还要返回 full_dispatch_request 中 finalize_request
def full_dispatch_request(self):
"""Dispatches the request and on top of that performs request
pre and postprocessing as well as HTTP exception catching and
error handling.
.. versionadded:: 0.7
"""
# 执行before_first_request 只执行一次 中间件
self.try_trigger_before_first_request_functions()
try:
# 触发信号 request_startd信号
request_started.send(self)
# 预处理 找到其中间件 看源码 貌似 调的app的蓝图
rv = self.preprocess_request()
if rv is None:
# 执行views
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
# 处理 视图 函数 完成 之后
return self.finalize_request(rv)
finalize_request就不在写 用处不大,看源码,其实就是类似request_class ,里边同样实现了 response_class 并且调用了make_response 变成了响应对象
响应完成之后还要 删除 本次请求的相关信息 将自己请求的所有数据在local中删除调
1.2.2===ctx.auto_pop(error)代码:
def auto_pop(self, exc):
if self.request.environ.get("flask._preserve_context") or (
exc is not None and self.app.preserve_context_on_exception
):
self.preserved = True
self._preserved_exc = exc
else:
self.pop(exc)
1.2.3==== pop
def pop(self, exc=_sentinel):
# 先删除 程序上下文
app_ctx = self._implicit_app_ctx_stack.pop()
try:
clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
self._preserved_exc = None
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
if hasattr(sys, "exc_clear"):
sys.exc_clear()
request_close = getattr(self.request, "close", None)
if request_close is not None:
request_close()
clear_request = True
finally:
rv = _request_ctx_stack.pop()
if clear_request:
rv.request.environ["werkzeug.request"] = None
if app_ctx is not None:
app_ctx.pop(exc)
assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
rv,
self,
)
1.2.4=====_request_ctx_stack.pop就等于LocalStack.pop
这里 stack 本质上是个 字典的 对象
def pop(self):
"""Removes the topmost item from the stack, will return the
old value or `None` if the stack was already empty.
"""
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
至此 flask整个 请求相应周期完成
总结:
1、需要 了解 threadlocal的原理
2、python的引用语义
3、python丰富的双下方法以及它们的使用 例如 __setitem__ 引发的死循环