以前の回答では、リクエスト中にFlaskのバックグラウンドで何が行われているかの概要がすでに示されています。まだ読んでいない場合は、読む前に@MarkHildrethの回答をお勧めします。つまり、httpリクエストごとに新しいコンテキスト(スレッド)が作成されLocal
ます。そのためrequest
、およびなどのオブジェクトを許可するスレッド機能が必要です。g
リクエスト固有のコンテキストを維持しながら、スレッド間でグローバルにアクセスできるようにする。さらに、httpリクエストを処理している間、Flaskは内部から追加のリクエストをエミュレートできるため、それぞれのコンテキストをスタックに保存する必要があります。また、Flaskを使用すると、単一のプロセス内で複数のwsgiアプリケーションを相互に実行できます。また、リクエスト中に複数のwsgiアプリケーションを呼び出してアクションを実行できます(各リクエストにより新しいアプリケーションコンテキストが作成されます)。したがって、アプリケーションのコンテキストスタックが必要です。これは、以前の回答でカバーされていた内容の要約です。
私の目標は、今説明することで、私たちの現在の理解を補完することでどのようにフラスコとWERKZEUGが、彼らはこれらのコンテキスト地元の人々と何ありません。ロジックの理解を深めるためにコードを簡略化しましたが、これを理解すれば、実際のソース(werkzeug.local
およびflask.globals
)のほとんどを簡単に理解できるはずです。
まず、Werkzeugがスレッドローカルを実装する方法を理解しましょう。
地元
HTTPリクエストが受信されると、シングルスレッドのコンテキスト内で処理されます。Werkzeugは、httpリクエスト中に新しいコンテキストを生成する代替手段として、通常のスレッドの代わりにグリーンレット(一種の軽い「マイクロスレッド」)の使用も許可します。Greenletがインストールされていない場合は、代わりにスレッドの使用に戻ります。これらのスレッド(またはグリーンレット)のそれぞれは、モジュールのget_ident()
関数で取得できる一意のIDで識別できます。その関数が持つ後ろのマジックを開始点であるrequest
、current_app
、url_for
、g
、および他のそのようなコンテキストバインドグローバルオブジェクト。
try:
from greenlet import get_ident
except ImportError:
from thread import get_ident
これでアイデンティティ関数ができたので、いつどのスレッドにいるかを知るLocal
ことができ、グローバルにアクセスできるコンテキストオブジェクトであるthreadを作成できますが、その属性にアクセスすると、それらの属性の値に解決されます。その特定のスレッド。例えば
# globally
local = Local()
# ...
# on thread 1
local.first_name = 'John'
# ...
# on thread 2
local.first_name = 'Debbie'
両方の値はグローバルにアクセス可能なLocal
オブジェクトに同時に存在しますがlocal.first_name
、スレッド1のコンテキスト内でアクセスするとが与えられ'John'
、'Debbie'
スレッド2で戻ります。
そんなことがあるものか?いくつかの(簡略化された)コードを見てみましょう:
class Local(object)
def __init__(self):
self.storage = {}
def __getattr__(self, name):
context_id = get_ident() # we get the current thread's or greenlet's id
contextual_storage = self.storage.setdefault(context_id, {})
try:
return contextual_storage[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
context_id = get_ident()
contextual_storage = self.storage.setdefault(context_id, {})
contextual_storage[name] = value
def __release_local__(self):
context_id = get_ident()
self.storage.pop(context_id, None)
local = Local()
上記のコードから、魔法が沸騰してget_ident()
現在のグリーンレットまたはスレッドを識別することがわかります。Local
ストレージは、その後、ちょうど現在のスレッドに任意のデータコンテキストを保存するためのキーとしてそれを使用します。
あなたは複数持つことができLocal
、プロセスごとのオブジェクトとrequest
、g
、current_app
および他の人は単にそのように作成されている可能性があります。しかし、それはFlaskで行われる方法ではなく、これらは技術的に Local
オブジェクトではなく、より正確にはLocalProxy
オブジェクトです。って何LocalProxy
?
LocalProxy
LocalProxyは、a Local
にクエリを実行して、別の関心のあるオブジェクト(つまり、それがプロキシするオブジェクト)を見つけるオブジェクトです。理解してみましょう:
class LocalProxy(object):
def __init__(self, local, name):
# `local` here is either an actual `Local` object, that can be used
# to find the object of interest, here identified by `name`, or it's
# a callable that can resolve to that proxied object
self.local = local
# `name` is an identifier that will be passed to the local to find the
# object of interest.
self.name = name
def _get_current_object(self):
# if `self.local` is truly a `Local` it means that it implements
# the `__release_local__()` method which, as its name implies, is
# normally used to release the local. We simply look for it here
# to identify which is actually a Local and which is rather just
# a callable:
if hasattr(self.local, '__release_local__'):
try:
return getattr(self.local, self.name)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.name)
# if self.local is not actually a Local it must be a callable that
# would resolve to the object of interest.
return self.local(self.name)
# Now for the LocalProxy to perform its intended duties i.e. proxying
# to an underlying object located somewhere in a Local, we turn all magic
# methods into proxies for the same methods in the object of interest.
@property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
raise AttributeError('__dict__')
def __repr__(self):
try:
return repr(self._get_current_object())
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
def __bool__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False
# ... etc etc ...
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
def __setitem__(self, key, value):
self._get_current_object()[key] = value
def __delitem__(self, key):
del self._get_current_object()[key]
# ... and so on ...
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
__str__ = lambda x: str(x._get_current_object())
__lt__ = lambda x, o: x._get_current_object() < o
__le__ = lambda x, o: x._get_current_object() <= o
__eq__ = lambda x, o: x._get_current_object() == o
# ... and so forth ...
グローバルにアクセスできるプロキシを作成するには、次のようにします
# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')
そして今、リクエストの過程の早い段階で、以前に作成されたプロキシがアクセスできるローカル内にいくつかのオブジェクトを保存します。
# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()
自分LocalProxy
で作成するのLocals
ではなく、グローバルにアクセスできるオブジェクトとして使用することの利点は、管理が簡単になることです。Local
グローバルにアクセス可能な多くのプロキシを作成するには、1つのオブジェクトのみが必要です。リクエストの最後に、クリーンアップ中に、1つを解放するLocal
(つまり、context_idをストレージからポップする)だけで、プロキシに煩わされることはありません。プロキシは引き続きグローバルにアクセス可能でありLocal
、オブジェクトを見つけるためにプロキシに委ねます後続のhttpリクエストに関心があります。
# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()
LocalProxy
すでにがある場合のの作成を簡略化するためにLocal
、Werkzeug Local.__call__()
は次のように魔法のメソッドを実装します。
class Local(object):
# ...
# ... all same stuff as before go here ...
# ...
def __call__(self, name):
return LocalProxy(self, name)
# now you can do
local = Local()
request = local('request')
g = local('g')
しかし、あなたはどのように、まだありませんフラスコソース(flask.globals)で見ればrequest
、g
、current_app
およびsession
作成されます。私たちが確立したように、Flaskは複数の「偽の」リクエストを(単一の真のhttpリクエストから)生成でき、その過程で複数のアプリケーションコンテキストもプッシュします。これは一般的なユースケースではありませんが、フレームワークの機能です。これらの「同時」リクエストとアプリは、いつでも「フォーカス」を持つ1つだけで実行されるように制限されているため、それぞれのコンテキストにスタックを使用することは理にかなっています。新しいリクエストが生成されるか、アプリケーションの1つが呼び出されると、それらのコンテキストがそれぞれのスタックの最上位にプッシュされます。FlaskはLocalStack
この目的でオブジェクトを使用します。彼らは彼らのビジネスを締結すると、スタックからコンテキストをポップします。
LocalStack
これはLocalStack
次のようになります(ここでも、ロジックが理解しやすくなるようにコードが簡略化されています)。
class LocalStack(object):
def __init__(self):
self.local = Local()
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self.local, 'stack', None)
if rv is None:
self.local.stack = rv = []
rv.append(obj)
return rv
def pop(self):
"""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)
if stack is None:
return None
elif len(stack) == 1:
release_local(self.local) # this simply releases the local
return stack[-1]
else:
return stack.pop()
@property
def top(self):
"""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
上記から、a LocalStack
はローカルに格納されたスタックであり、スタックに格納されたローカルの束ではないことに注意してください。これは、スタックはグローバルにアクセス可能ですが、スレッドごとに異なるスタックであることを意味します。
フラスコはその必要はありませんrequest
、current_app
、g
、とはsession
直接解決オブジェクトLocalStack
、それはむしろ使用するLocalProxy
オブジェクトをそのラップルックアップ関数(の代わりLocal
の基礎となるオブジェクトを見つけるオブジェクト)LocalStack
:
_request_ctx_stack = LocalStack()
def _find_request():
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return top.request
request = LocalProxy(_find_request)
def _find_session():
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return top.session
session = LocalProxy(_find_session)
_app_ctx_stack = LocalStack()
def _find_g():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.g
g = LocalProxy(_find_g)
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.app
current_app = LocalProxy(_find_app)
これらはすべてアプリケーションの起動時に宣言されますが、要求コンテキストまたはアプリケーションコンテキストがそれぞれのスタックにプッシュされるまで、実際には何にも解決されません。
コンテキストがスタックに実際に挿入される(そして次にポップアウトされる)flask.app.Flask.wsgi_app()
方法を知りたい場合は、wsgiアプリのエントリポイント(つまり、Webサーバーが呼び出し、http環境をリクエストが受信されます)、RequestContext
その後のpush()
intoに至るまで、オブジェクトの作成に従います_request_ctx_stack
。スタックの一番上にプッシュすると、からアクセスできます_request_ctx_stack.top
。以下は、フローを示すためのいくつかの省略コードです。
アプリを起動して、WSGIサーバーで利用できるようにします...
app = Flask(*config, **kwconfig)
# ...
後でhttpリクエストが届き、WSGIサーバーが通常のパラメーターでアプリを呼び出します...
app(environ, start_response) # aka app.__call__(environ, start_response)
これはおおよそアプリで何が起こるかです...
def Flask(object):
# ...
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_response):
ctx = RequestContext(self, environ)
ctx.push()
try:
# process the request here
# raise error if any
# return Response
finally:
ctx.pop()
# ...
これは、おおよそRequestContextで何が起こるかです...
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.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
self.flashes = None
def push(self):
_request_ctx_stack.push(self)
def pop(self):
_request_ctx_stack.pop()
リクエストの初期化が完了したとすると、request.path
ビュー関数の1 つからのルックアップは次のようになります。
- グローバルにアクセス可能な
LocalProxy
オブジェクトから開始しますrequest
。
- 基になる対象オブジェクト(プロキシの対象となるオブジェクト)を見つけるには、ルックアップ関数
_find_request()
(関数として登録された関数)を呼び出しますself.local
。
- この関数は、スタック上の最上位コンテキストの
LocalStack
オブジェクト_request_ctx_stack
を照会します。
- 最上位のコンテキストを見つけるために、
LocalStack
オブジェクトは最初にその内部Local
属性(self.local
)stack
に以前に格納されたプロパティを照会します。
stack
それはトップのコンテキストを取得します
- そして
top.request
これ関心の基礎となるオブジェクトとして解決されます。
- そのオブジェクトから
path
属性を取得します
私たちはどのように見てきたのでLocal
、LocalProxy
、とLocalStack
仕事を、今取得中意味合いやニュアンスの瞬間のためだと思うpath
から。
request
シンプルなグローバルアクセス可能なオブジェクトになりますオブジェクト。
request
地元のだろうオブジェクト。
request
ローカルの属性として保存されたオブジェクト。
request
ローカルに格納されたオブジェクトへのプロキシであるオブジェクト。
request
スタックに格納されているオブジェクト、つまりローカルに格納されているオブジェクト。
request
ローカルに格納されているスタック上のオブジェクトへのプロキシであるオブジェクト。<-これがFlaskの機能です。