flask是如何管理上下文的
1.什么是上下文
通俗的说,就是一个容器,存储了一些与程序运行相关的参数变量等,这些内容支持着程序的运行。
在flask中谈论的上下文为两个:请求上下文和应用上下文。比如常用的g、session、request,属于请求上下文,其内容只在各自的请求中有效。而current_app就是应用上下文。flask引入应用上下文的概念是为了更好的支持多应用开发。
2.flask是如何管理上下文的
2.1 继续从上篇文章falsk是如何处理请求的接着说。上篇文章说到wsgi_app时,提到了调用self.request_context方法时会创建请求上下文对象。
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)
2.2 那就先看一下self.request_context执行了什么(已删减部分源码)。内部首先是实例化了RequestContext这个类(这个名字很明显吧),实例化方法内部还实例化了request_class这个类(继承自BaseRequest),返回了请求对象,这个请求对象包含了请求的相关信息。RequestContext类中实现了两个重要的方法:push和pop方法。看到这里,依稀明白了请求上下文的处理流程。
上面wsgi_app中提到的full_dispatch_request方法在处理请求时,会到_request_ctx_stack取栈顶的请求上下文(可继续看源码,内容太多,就不贴出来了),对请求处理结束返回相应的响应对象后,再调用auto_pop(内部调用pop)将请求上下文从栈空间弹出。
flask是支持多线程和协程的,比如多线程访问时,flask是如何保证取请求上下文不会取到同一个呢?
def request_context(self, environ):
# self即当前app,environ是请求的参数
return RequestContext(self, environ)
class RequestContext(object):
def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self._implicit_app_ctx_stack = []
self.preserved = False
def push(self):
"""Binds the request context to the current context."""
# 取_request_ctx_stack栈顶的请求上下文
top = _request_ctx_stack.top
# 如果某次异常,会导致上次请求上下文没有正常弹出,这里确保栈顶没有请求上下文
if top is not None and top.preserved:
top.pop(top._preserved_exc)
# 取_app_ctx_stack栈顶的应用上下文
app_ctx = _app_ctx_stack.top
# 确保当前请求上下文在这个应用上下文内
if app_ctx is None or app_ctx.app != self.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()
# 将自身压入_request_ctx_stack栈中
_request_ctx_stack.push(self)
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
# Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)
# 确保弹出的上下文空间是自身
assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
rv,
self,
)
2.3 答案是使用线程或协程的唯一标识,即get_ident这个函数。来看_request_ctx_stack源码,_request_ctx_stack是个全局变量,一开始实例化Flask类时,就会实例化LocalStack这个类并且导入了这个变量。
调用push方法时,会触发self._local的__getattr__方法,如果self._local没有存储当前线程或协程的唯一标识,会触发自身的__setattr__方法,然后将当前请求上下文存储到这个__storage__属性中,这样就保证并发请求时,正确使用对应的上下文啦。
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
class Local(object):
__slots__ = ("__storage__", "__ident_func__")
def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
class LocalStack(object):
def __init__(self):
self._local = Local()
def push(self, obj):
"""Pushes a new item to the stack"""
# 调用self._local对象中的__getattr__方法
rv = getattr(self._local, "stack", None)
if rv is None:
# 调用self._local对象中的__setattr__方法,设置当前线程或协程唯一标识
self._local.stack = rv = []
# 压入栈中当前的请求上下文
# 最终self._local中__storage__的内容类似为:
# {241253254325: {'stack': RequestContext}}
rv.append(obj)
return rv
def pop(self):
pass
@property
def top(self):
pass