什么是上下文?
flask框架中的上下文本质上就是两个类,我们可以先看一下他的初始化函数:
应用上下文
class AppContext(object):
"""The application context binds an application object implicitly
to the current thread or greenlet, similar to how the
:class:`RequestContext` binds request information. The application
context is also implicitly created if a request context is created
but the application is not on top of the individual application
context.
"""
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
# Like request context, app contexts can be pushed multiple times
# but there a basic "refcount" is enough to track them.
self._refcnt = 0
请求上下文
class RequestContext(object):
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
self._implicit_app_ctx_stack = []
self.preserved = False
self._preserved_exc = None
self._after_request_functions = []
self.match_request()
为什么设计上下文这样的机制?
详细解释可参考:
https://blog.tonyseek.com/post/the-context-mechanism-of-flask/
- 为了实现线程之间的隔离
类似Thread Local ,每个线程对一个 Thread Local 对象的修改都不会影响其他线程。这种对象的实现原理也非常简单,只要以线程的 ID 来保存多份状态字典即可,就像按照门牌号隔开的一格一格的信箱。
- 为了实现一个 Python 进程中拥有多个应用
from werkzeug.wsgi import DispatcherMiddleware
from biubiu.app import create_app
from biubiu.admin.app import create_app as create_admin_app
application = DispatcherMiddleware(create_app(), {
'/admin': create_admin_app()
})
上下文机制依赖的数据结构
flask上下文机制的实现基于 Werkzeug 的 Local Stack 实现。
阅读源码,我们发现Local类的本质是一个字典和一个获取到线程id的函数。
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
而LocalStack在Local类的基础上又实现了栈的功能。
class LocalStack(object):
def __init__(self):
self._local = Local()
flask中应用上下文栈和请求上下文栈正是基于上面的LocalStack类
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
下面我们通过一些实验来进一步学习flask的执行过程:
In [1]: from flask.globals import _app_ctx_stack, _request_ctx_stack
In [2]: from flask import Flask
In [3]: app = Flask(__name__)
In [4]: _app_ctx_stack._local.__storage__
Out[4]: {}
In [5]: _request_ctx_stack._local.__storage__
Out[5]: {}
In [6]: req_ctx = app.test_request_context()
In [7]: req_ctx.push()
In [8]: _request_ctx_stack._local.__storage__
Out[8]: {<greenlet.greenlet at 0x7f69d45b8f20>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
In [9]: _app_ctx_stack._local.__storage__
Out[9]: {<greenlet.greenlet at 0x7f69d45b8f20>: {'stack': [<flask.ctx.AppContext at 0x7f69d4774cf8>]}}
我们可以看到一开始上下文均为空,test_request_context()函数会生成一个请求上下文,我们通过push让它入栈,之后两个上下文都有了内容,为什么_app_ctx_stack中也有内容呢?
我们可以看一下源码,第一次请求上下文push时,app_ctx如果为None,就会调用implicit_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)
这里我们就可以解释wsgi_app这部分代码,每次web服务器提供了environ变量,就会创建一个request_context对象,push()
之后就会进入_request_ctx_stack中,并在执行处理函数之后会自动pop,
所以我们说请求上下文的生命周期就是一次请求的过程。
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:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)