Flask原始碼閱讀
阿新 • • 發佈:2022-03-05
上下文篇
整個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)
此時此刻,非我莫屬