整个Flask生命周期中都依赖LocalStack()
栈?。而LocalStack()
分为请求上下文_request_ctx_stack
和应用上下文_app_ctx_stack
.
_request_ctx_stack
:包含request
和session
等请求信息
_app_ctx_stack
:包含应用信息
... def _lookup_req_object(name): print("_lookup_req_object===", name) top = _request_ctx_stack.top print("top===", top) if top is None: raise RuntimeError(_request_ctx_err_msg) print("getattr(top, name)", getattr(top, name)) return getattr(top, name) def _lookup_app_object(name): print("_lookup_app_object===", name) top = _app_ctx_stack.top print("top===", top) if top is None: raise RuntimeError(_app_ctx_err_msg) print("getattr(top, name)", getattr(top, name)) return getattr(top, name) def _find_app(): top = _app_ctx_stack.top print("find_app", top) if top is None: raise RuntimeError(_app_ctx_err_msg) print("top.app", top.app) return top.app # 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'))
接下来我们看看LocalStack()
的内容,有一个Local()
类 、push()
方法、pop()
方法、top()
方法,还有一个通过列表维护成栈的stack
Local()
:LocalStack()
的核心push()
: 往stack
中推送数据pop()
:弹出stack
中数据top()
:返回stack
顶元素stack
:一个列表 []class LocalStack: def __init__(self) -> None: self._local = Local() def __release_local__(self) -> None: self._local.__release_local__() @property def __ident_func__(self) -> t.Callable[[], int]: return self._local.__ident_func__ @__ident_func__.setter def __ident_func__(self, value: t.Callable[[], int]) -> None: object.__setattr__(self._local, "__ident_func__", value) def __call__(self) -> "LocalProxy": def _lookup() -> t.Any: rv = self.top if rv is None: raise RuntimeError("object unbound") return rv return LocalProxy(_lookup) def push(self, obj: t.Any) -> t.List[t.Any]: """Pushes a new item to the stack""" rv = getattr(self._local, "stack", []).copy() rv.append(obj) print("stack0000000000000", rv) self._local.stack = rv print("self.local00000000", self._local._storage) print("self.__ident_func__00000000", self._local.__ident_func__) return rv # type: ignore def pop(self) -> t.Any: """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) print("stack111111111", stack) print("self.local111111111", self._local._storage) print("self.__ident_func__11111111", self._local.__ident_func__) if stack is None: return None elif len(stack) == 1: release_local(self._local) return stack[-1] else: return stack.pop() @property def top(self) -> t.Any: """The topmost item on the stack. If the stack is empty, `None` is returned. """ try: return self._local.stack[-1] except (AttributeError, IndexError): return None
来到Local()
我们看到有一个_storage
,_storage
而核心是 ContextVar("local_storage")
class Local: __slots__ = ("_storage",) def __init__(self) -> None: object.__setattr__(self, "_storage", ContextVar("local_storage")) @property def __storage__(self) -> t.Dict[str, t.Any]: warnings.warn( "'__storage__' is deprecated and will be removed in Werkzeug 2.1.", DeprecationWarning, stacklevel=2, ) return self._storage.get({}) # type: ignore @property def __ident_func__(self) -> t.Callable[[], int]: warnings.warn( "'__ident_func__' is deprecated and will be removed in" " Werkzeug 2.1. It should not be used in Python 3.7+.", DeprecationWarning, stacklevel=2, ) return _get_ident # type: ignore @__ident_func__.setter def __ident_func__(self, func: t.Callable[[], int]) -> None: warnings.warn( "'__ident_func__' is deprecated and will be removed in" " Werkzeug 2.1. Setting it no longer has any effect.", DeprecationWarning, stacklevel=2, ) def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]: return iter(self._storage.get({}).items()) def __call__(self, proxy: str) -> "LocalProxy": """Create a proxy for a name.""" return LocalProxy(self, proxy) def __release_local__(self) -> None: __release_local__(self._storage) def __getattr__(self, name: str) -> t.Any: values = self._storage.get({}) print(values, name, "xxxxxxxxxxxxx") try: return values[name] except KeyError: raise AttributeError(name) from None def __setattr__(self, name: str, value: t.Any) -> None: values = self._storage.get({}).copy() values[name] = value print(name, values, "xxxxxxxxxx222xxxxxx") self._storage.set(values) def __delattr__(self, name: str) -> None: values = self._storage.get({}).copy() try: del values[name] self._storage.set(values) except KeyError: raise AttributeError(name) from None
进入ContextVar
我们会发现ContextVar
有两个,一个是系统的ContextVar
另一个是本地维护ContextVar
类。第一选择使用的是系统的ContextVar
和greenlet
协程,我们主动报错,使其使用本地维护的ContextVar
类。这个ContextVar类就是维护一个全局字典,这个字典是线程安全的关键,每个请求对应一个线程ID,通过这个全局字典来维护。
{9064: {'stack': [<flask.ctx.AppContext object at 0x0000016B7E27B748>]}} # 应用上下文
{9064: {'stack': [<RequestContext 'http://127.0.0.1:5000/22' [GET] of test_testing>]}} # 请求上下文
try: from contextvars import ContextVar # 主动报错,自己维护ContextVar raise ImportError("xxxx") if "gevent" in sys.modules or "eventlet" in sys.modules: # Both use greenlet, so first check it has patched # ContextVars, Greenlet <0.4.17 does not. import greenlet greenlet_patched = getattr(greenlet, "GREENLET_USE_CONTEXT_VARS", False) if not greenlet_patched: # If Gevent is used, check it has patched ContextVars, # <20.5 does not. try: from gevent.monkey import is_object_patched except ImportError: # Gevent isn't used, but Greenlet is and hasn't patched raise _CannotUseContextVar() from None else: if is_object_patched("threading", "local") and not is_object_patched( "contextvars", "ContextVar" ): raise _CannotUseContextVar() def __release_local__(storage: t.Any) -> None: # Can remove when support for non-stdlib ContextVars is # removed, see "Fake" version below. storage.set({}) except (ImportError, _CannotUseContextVar): class ContextVar: # type: ignore """A fake ContextVar based on the previous greenlet/threading ident function. Used on Python 3.6, eventlet, and old versions of gevent. """ def __init__(self, _name: str) -> None: self.storage: t.Dict[int, t.Dict[str, t.Any]] = {} def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: print(self.storage, _get_ident(), default, "1111111") return self.storage.get(_get_ident(), default) def set(self, value: t.Dict[str, t.Any]) -> None: self.storage[_get_ident()] = value print(self.storage, "000000") def __release_local__(storage: t.Any) -> None: # Special version to ensure that the storage is cleaned up on # release. # 释放栈 print("storage.storage", _get_ident(), storage.storage) storage.storage.pop(_get_ident(), None) ... try: from greenlet import getcurrent as _get_ident raise ImportError("xxxx") except ImportError: from threading import get_ident as _get_ident def get_ident() -> int: warnings.warn( "'get_ident' is deprecated and will be removed in Werkzeug" " 2.1. Use 'greenlet.getcurrent' or 'threading.get_ident' for" " previous behavior.", DeprecationWarning, stacklevel=2, ) return _get_ident() # type: ignore
此处有图,后面再补
- 启动时调用
__call__
方法
class Flask(_PackageBoundObject): ... # step 1 def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`.""" return self.wsgi_app(environ, start_response)
- 初始化请求上下文
class Flask(_PackageBoundObject): ... def wsgi_app(self, environ, start_response): # 初始化请求上下文 # step 2 ctx = self.request_context(environ) # 将请求上下文 推进_request_ctx_stack栈中 # step 3 ctx.push() error = None try: try: # 分发请求 获取结果 # step 4 response = self.full_dispatch_request() except Exception as e: error = e response = self.handle_exception(e) except: error = sys.exc_info()[1] raise # 返回结果 # step 5 return response(environ, start_response) finally: if self.should_ignore_error(error): error = None # 弹出_request_ctx_stack、_app_ctx_stack栈数据 # step 6 ctx.auto_pop(error)
- 将请求推入
_request_ctx_stack
栈中和应用推入_app_ctx_stack
栈中
class RequestContext(object): ... def push(self): """Binds the request context to the current context.""" # If an exception occurs in debug mode or if context preservation is # activated under exception situations exactly one context stays # on the stack. The rationale is that you want to access that # information under debug situations. However if someone forgets to # pop that context again we want to make sure that on the next push # it's invalidated, otherwise we run at risk that something leaks # memory. This is usually only a problem in test suite since this # functionality is not active in production environments. top = _request_ctx_stack.top print("_request_ctx_stack.top", 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. # 初始化应用上下文 # step 3.1 print("_app_ctx_stack", _app_ctx_stack) app_ctx = _app_ctx_stack.top print("_app_ctx_stack.top", app_ctx) if app_ctx is None or app_ctx.app != self.app: # 初始化应用上下文 app_ctx = self.app.app_context() print("app_ctx", app_ctx) # 将应用上下文推入应用上下文栈中 app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) print("self._implicit_app_ctx_stack", self._implicit_app_ctx_stack) if hasattr(sys, 'exc_clear'): sys.exc_clear() # step 3.2 _request_ctx_stack.push(self) print("_request_ctx_stack", _request_ctx_stack, 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). # 处理session # step 3.3 self.session = self.app.open_session(self.request) print("self.session", self.session) if self.session is None: self.session = self.app.make_null_session() print("self.session", self.session) class AppContext(object): ... 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)
- 派发、处理请求
class Flask(_PackageBoundObject): ... # step 4.1 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 """ 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) # step 4.2 def dispatch_request(self): """Does the request dispatching. Matches the URL and returns the return value of the view or error handler. This does not have to be a response object. In order to convert the return value to a proper response object, call :func:`make_response`. .. versionchanged:: 0.7 This no longer does the exception handling, this code was moved to the new :meth:`full_dispatch_request`. """ 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 print("rule.endpoint", rule.endpoint) print("self.view_functions", self.view_functions, "req.view_args", req.view_args) # 执行views return self.view_functions[rule.endpoint](**req.view_args) # step 4.3 def finalize_request(self, rv, from_error_handler=False): """Given the return value from a view function this finalizes the request by converting it into a response and invoking the postprocessing functions. This is invoked for both normal request dispatching as well as error handlers. Because this means that it might be called as a result of a failure a special safe mode is available which can be enabled with the `from_error_handler` flag. If enabled, failures in response processing will be logged and otherwise ignored. :internal: """ response = self.make_response(rv) try: # 处理响应 response = self.process_response(response) request_finished.send(self, response=response) except Exception: if not from_error_handler: raise self.logger.exception('Request finalizing failed with an ' 'error while handling an error') return response # step 4.4 def process_response(self, response): """Can be overridden in order to modify the response object before it's sent to the WSGI server. By default this will call all the :meth:`after_request` decorated functions. .. versionchanged:: 0.5 As of Flask 0.5 the functions registered for after request execution are called in reverse order of registration. :param response: a :attr:`response_class` object. :return: a new response object or the same, has to be an instance of :attr:`response_class`. """ ctx = _request_ctx_stack.top bp = ctx.request.blueprint funcs = ctx._after_request_functions if bp is not None and bp in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[bp])) if None in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[None])) for handler in funcs: response = handler(response) # step 4.4.1 if not self.session_interface.is_null_session(ctx.session): # 增加session self.save_session(ctx.session, response) return response
- 从
_request_ctx_stack
、_app_ctx_stack
栈中弹出当前请求上下文、应用上下文
class RequestContext(object): ... # step 6.2 def pop(self, exc=_sentinel): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. .. versionchanged:: 0.9 Added the `exc` argument. """ 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 this interpreter supports clearing the exception information # we do that now. This will only go into effect on Python 2.x, # on 3.x it disappears automatically at the end of the exception # stack. 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() # get rid of circular dependencies at the end of the request # so that we don't require the GC to be active. 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) # step 6.1 def auto_pop(self, exc): print("auto_pop", 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)