Jawaban sebelumnya sudah memberikan gambaran yang bagus tentang apa yang terjadi di latar belakang Flask selama permintaan. Jika Anda belum membacanya saya merekomendasikan jawaban @ MarkHildreth sebelum membaca ini. Singkatnya, konteks baru (utas) dibuat untuk setiap permintaan http, itulah sebabnya mengapa perlu ada utasLocal
fasilitas yang memungkinkan objek seperti request
dang
untuk dapat diakses secara global di seluruh utas, sambil mempertahankan konteks permintaan khusus mereka. Lebih lanjut, saat memproses permintaan http, Flask dapat mengemulasi permintaan tambahan dari dalam, karenanya perlunya menyimpan konteksnya masing-masing pada tumpukan. Juga, Flask memungkinkan beberapa aplikasi wsgi untuk berjalan bersama dalam satu proses tunggal, dan lebih dari satu dapat dipanggil untuk bertindak selama permintaan (setiap permintaan membuat konteks aplikasi baru), maka kebutuhan untuk tumpukan konteks untuk aplikasi. Itu adalah ringkasan dari apa yang dicakup dalam jawaban sebelumnya.
Tujuan saya sekarang adalah untuk melengkapi pemahaman kita saat ini dengan menjelaskan bagaimana Flask dan Werkzeug melakukan apa yang mereka lakukan dengan penduduk setempat. Saya menyederhanakan kode untuk meningkatkan pemahaman logikanya, tetapi jika Anda mendapatkan ini, Anda harus dapat dengan mudah memahami sebagian besar apa yang ada di sumber yang sebenarnya ( werkzeug.local
danflask.globals
).
Pertama-tama mari kita memahami bagaimana Werkzeug mengimplementasikan thread Locals.
Lokal
Ketika permintaan http masuk, diproses dalam konteks utas tunggal. Sebagai alternatif untuk menelurkan konteks baru selama permintaan http, Werkzeug juga memungkinkan penggunaan greenlets (semacam "thread mikro" yang lebih ringan) alih-alih thread normal. Jika Anda tidak menginstal greenlets, maka ia akan kembali menggunakan utas. Masing-masing utas ini (atau greenlets) dapat diidentifikasi oleh id unik, yang dapat Anda ambil dengan fungsi modul get_ident()
. Fungsi itu adalah titik mulai keajaiban di balik memiliki request
, current_app
, url_for
, g
, dan konteks-terikat benda lain seperti global.
try:
from greenlet import get_ident
except ImportError:
from thread import get_ident
Sekarang setelah kita memiliki fungsi identitas kita, kita dapat mengetahui utas mana yang sedang kita pakai pada waktu tertentu dan kita dapat membuat apa yang disebut utas Local
, objek kontekstual yang dapat diakses secara global, tetapi ketika Anda mengakses atributnya mereka memutuskan untuk nilainya bagi utas spesifik itu. misalnya
# globally
local = Local()
# ...
# on thread 1
local.first_name = 'John'
# ...
# on thread 2
local.first_name = 'Debbie'
Kedua nilai hadir pada Local
objek yang dapat diakses secara global pada saat yang sama, tetapi mengakses local.first_name
dalam konteks utas 1 akan memberi Anda 'John'
, sedangkan itu akan kembali'Debbie'
pada utas 2.
Bagaimana mungkin? Mari kita lihat beberapa kode (disederhanakan):
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()
Dari kode di atas kita dapat melihat bahwa sihir bermuara di get_ident()
mana mengidentifikasi greenlet atau utas saat ini. ItuLocal
penyimpanan kemudian hanya menggunakan itu sebagai kunci untuk menyimpan data kontekstual untuk thread saat ini.
Anda dapat memiliki beberapa Local
objek per proses dan request
, g
, current_app
dan lain-lain bisa hanya telah diciptakan seperti itu. Tapi itu bukan bagaimana hal itu dilakukan dalam termos di mana ini bukan objek secara teknis Local
, tetapi lebih tepatnya LocalProxy
objek. Apa ituLocalProxy
?
Proxy Lokal
LocalProxy adalah objek yang meminta a Local
untuk menemukan objek lain yang menarik (yaitu objek yang diproksikan). Mari kita lihat untuk memahami:
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 ...
Sekarang untuk membuat proxy yang dapat diakses secara global yang akan Anda lakukan
# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')
dan sekarang beberapa waktu lebih awal selama permintaan Anda akan menyimpan beberapa objek di dalam lokal yang dapat diakses oleh proxy yang dibuat sebelumnya, tidak peduli di mana thread kami berada
# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()
Keuntungan menggunakan LocalProxy
objek yang dapat diakses secara global daripada membuatnya Locals
sendiri adalah menyederhanakan pengelolaannya. Anda hanya perlu satu Local
objek untuk membuat banyak proxy yang dapat diakses secara global. Pada akhir permintaan, selama pembersihan, Anda cukup melepaskan satu Local
(yaitu Anda pop konteks_id dari penyimpanannya) dan tidak repot-repot dengan proxy, mereka masih dapat diakses secara global dan masih tunduk kepada yang Local
menemukan objek mereka menarik untuk permintaan http berikutnya.
# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()
Untuk menyederhanakan pembuatan LocalProxy
ketika kita sudah memiliki Local
, Werkzeug mengimplementasikan Local.__call__()
metode ajaib sebagai berikut:
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')
Namun, jika Anda melihat pada sumber Flask (flask.globals) itu masih belum seberapa request
, g
, current_app
dan session
diciptakan. Seperti yang telah kami tentukan, Flask dapat menelurkan beberapa permintaan "palsu" (dari satu permintaan http sejati) dan dalam proses itu juga mendorong beberapa konteks aplikasi. Ini bukan kasus penggunaan umum, tapi itu adalah kemampuan dari kerangka kerja. Karena permintaan dan aplikasi "bersamaan" ini masih terbatas untuk dijalankan dengan hanya satu yang memiliki "fokus" kapan saja, masuk akal untuk menggunakan tumpukan untuk konteks masing-masing. Setiap kali permintaan baru muncul atau salah satu aplikasi dipanggil, mereka mendorong konteksnya di bagian atas tumpukan masing-masing. Labu menggunakan LocalStack
objek untuk tujuan ini. Ketika mereka menyimpulkan bisnis mereka, mereka mengeluarkan konteks dari tumpukan.
Tumpukan lokal
Seperti inilah LocalStack
bentuknya (sekali lagi kode ini disederhanakan untuk memudahkan pemahaman logikanya).
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
Perhatikan dari atas bahwa a LocalStack
adalah tumpukan yang disimpan di lokal, bukan sekelompok penduduk setempat yang disimpan di tumpukan. Ini menyiratkan bahwa meskipun tumpukan dapat diakses secara global, itu adalah tumpukan yang berbeda di setiap utas.
Flask tidak memiliki nya request
, current_app
, g
, dan session
benda menyelesaikan langsung ke LocalStack
, agak menggunakan LocalProxy
benda-benda yang membungkus fungsi lookup (bukan Local
objek) yang akan menemukan objek yang mendasari dari 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)
Semua ini dinyatakan pada saat permulaan aplikasi, tetapi tidak benar-benar menyelesaikan apa pun sampai konteks permintaan atau konteks aplikasi didorong ke tumpukan masing-masing.
Jika Anda penasaran untuk melihat bagaimana konteks sebenarnya dimasukkan dalam tumpukan (dan kemudian muncul), lihat di flask.app.Flask.wsgi_app()
mana merupakan titik masuknya aplikasi wsgi (yaitu apa yang disebut server web dan berikan lingkungan http ketika sebuah permintaan masuk), dan ikuti pembuatan RequestContext
objek semua melalui selanjutnya push()
ke _request_ctx_stack
. Setelah didorong di bagian atas tumpukan, dapat diakses melalui _request_ctx_stack.top
. Berikut beberapa kode singkat untuk menunjukkan alur:
Jadi Anda memulai aplikasi dan membuatnya tersedia untuk server WSGI ...
app = Flask(*config, **kwconfig)
# ...
Kemudian permintaan http muncul dan server WSGI memanggil aplikasi dengan params yang biasa ...
app(environ, start_response) # aka app.__call__(environ, start_response)
Ini kira-kira yang terjadi di aplikasi ...
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()
# ...
dan inilah kira-kira yang terjadi dengan 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()
Katakanlah permintaan telah selesai diinisialisasi, karena itu pencarian request.path
dari salah satu fungsi tampilan Anda akan berjalan sebagai berikut:
- mulai dari
LocalProxy
objek yang dapat diakses secara global request
.
- untuk menemukan objek yang mendasarinya (objek tempat proxy) memanggil fungsi pencariannya
_find_request()
(fungsi yang didaftarkan sebagaiself.local
).
- fungsi itu menanyakan
LocalStack
objek_request_ctx_stack
untuk konteks teratas pada stack.
- untuk menemukan konteks teratas,
LocalStack
objek pertama kali meminta Local
atribut batinnya ( self.local
) untukstack
properti yang sebelumnya disimpan di sana.
- dari
stack
itu mendapat konteks teratas
- dan
top.request
dengan demikian diselesaikan sebagai objek yang menarik.
- dari objek itu kita mendapatkan
path
atribut
Jadi kita telah melihat bagaimana Local
, LocalProxy
dan LocalStack
bekerja, sekarang berpikir sejenak tentang implikasi dan nuansa dalam mengambil path
dari:
- sebuah
request
objek yang akan menjadi sederhana global objek diakses.
- sebuah
request
objek yang akan menjadi lokal.
- sebuah
request
objek yang tersimpan sebagai atribut lokal.
- Sebuah
request
objek yang merupakan proxy untuk sebuah objek disimpan dalam lokal.
- Sebuah
request
objek yang tersimpan pada stack, yang pada gilirannya disimpan dalam lokal.
- sebuah
request
objek yang merupakan proxy untuk objek pada tumpukan disimpan dalam lokal. <- inilah yang dilakukan Flask.