def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
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()
#其它函数
# with语句协议
def __enter__(self):
self.push()
return self
def __exit__(self, exc_type, exc_value, tb):
# do not pop the request stack if we are in debug mode and an
# exception happened. This will allow the debugger to still
# access the request object in the interactive shell. Furthermore
# the context can be force kept alive for the test client.
# See flask.testing for how this works.
self.auto_pop(exc_value)
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)
在init函数我们可以看到有这些东西将被保存到RequestContext中:
- app
- request
- url_adapter
- flashes
- session
其中request = app.request_class(environ)中的request_class是Flask类的一个class variable. 默认情况下,request_class = Request, 在wrappers.py 我们查看Request类:
class Request(RequestBase):
url_rule = None
view_args = None
routing_exception = None
_is_old_module = False
@property
def max_content_length(self):
...
@property
def endpoint(self):
...
@property
def module(self):
...
@property
def blueprint(self):
...
@property
def json(self):
...
@property
def is_json(self):
...
def get_json(self, force=False, silent=False, cache=True):
...
def on_json_loading_failed(self, e):
...
Request继承自werkzeug的Request类(示例),通过传入environ进行初始化,获得url_rule, view_args, endpoint等信息(示例)。
init函数最后会调用match_request():
def match_request(self):
"""Can be overridden by a subclass to hook into the matching
of the request.
"""
try:
url_rule, self.request.view_args = \
self.url_adapter.match(return_rule=True)
self.request.url_rule = url_rule
except HTTPException as e:
self.request.routing_exception = e
match_request函数配置了路由信息,可参考werkzeug路由示例。
下面我们做实验看看request里面到底有些啥,
至此,ctx = self.request_context(environ) 执行完毕。ctx为一个RequestContext对象。
接下来ctx.psuh():
def push(self):
"""Binds the request context to the current context."""
# 这3行代码跟调试异常有关,暂时忽略。
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
# 在push Request Context之前先保证push App Context。
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.push(self)
# Open the session at the moment that the request context is
# available. This allows a custom open_session method to use the
# request context (e.g. code that access database information
# stored on `g` instead of the appcontext).
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
首先获取app_ctx, app_ctx = self.app.app_context():
def app_context(self):
return AppContext(self)
class AppContext(object):
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
def push(self):
"""Binds the app context to the current context."""
self._refcnt += 1
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_app_ctx_stack.push(self)
appcontext_pushed.send(self.app)
与Request Context相比 AppContext含有的内容只有:
- app
- url_adapter
- g
相比RequestContext中的url_adapter,AppContext中提供给app.create_url_adapter的参数是None,那么AppContext只需要server name信息就足够了。
def create_url_adapter(self, request):
if request is not None:
return self.url_map.bind_to_environ(request.environ,
server_name=self.config['SERVER_NAME'])
# We need at the very least the server name to be set for this
# to work.
if self.config['SERVER_NAME'] is not None:
return self.url_map.bind(
self.config['SERVER_NAME'],
script_name=self.config['APPLICATION_ROOT'] or '/',
url_scheme=self.config['PREFERRED_URL_SCHEME'])
对于g变量,app_ctx_globals_class = _AppCtxGlobals,即g变量是_AppCtxGlobals的一个实例:
class _AppCtxGlobals(object):
"""A plain object."""
def get(self, name, default=None):
return self.__dict__.get(name, default)
def pop(self, name, default=_sentinel):
if default is _sentinel:
return self.__dict__.pop(name)
else:
return self.__dict__.pop(name, default)
def setdefault(self, name, default=None):
return self.__dict__.setdefault(name, default)
def __contains__(self, item):
return item in self.__dict__
def __iter__(self):
return iter(self.__dict__)
def __repr__(self):
top = _app_ctx_stack.top
if top is not None:
return '<flask.g of %r>' % top.app.name
return object.__repr__(self)
换句话说,g就是一个普通的对象,具体用法可参考flask.g.
app_ctx = self.app.app_context()执行完毕,执行app_ctx.push(),app context可以推送多次,但是一个基本的refcount足以来跟踪它们。_app_ctx_stack.push(self), 在globals.py中,我们看到:
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
无论是_request_ctx_stack,还是_app_ctx_stack都是werkzeug中的LocalStack 实例,而current_app, request, session, g都是LocalProxy实例,关于LocalStack和LocalProxy的用法可参见另一篇文章。与AppContext相关的是_app_ctx_stack, current_app, g.
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app
由于current_app是个代理对象,当我们对current_app进行操作时,实际上是对_find_app函数中返回的top.app进行操作的,即 _app_ctx_stack.top.app, 也即是AppContext().app(一个Flask类实例)进行操作。同样 ,对g进行操作,会转交给getattr(_app_ctx_stack.top, 'g')也即是_AppCtxGlobals类的实例。至此AppContext push完毕。
回到push RequestContext过程中,_request_ctx_stack.push(self),过程同上,request, session是与RequestContext相关的变量。
因此,当我们写from flask import current_app, request, session, g时,这些变量看似是全局变量,实际上则是LocalProxy类实例。最后push()函数中还有几行与session有关的代码,暂时忽略。
网友评论