Các câu trả lời trước đã đưa ra một cái nhìn tổng quan đẹp về những gì diễn ra trong nền của Flask trong một yêu cầu. Nếu bạn chưa đọc nó, tôi khuyên bạn nên trả lời @ MarkHildreth trước khi đọc nó. Nói tóm lại, một bối cảnh mới (luồng) được tạo cho mỗi yêu cầu http, đó là lý do tại sao cần có một Local
tiện ích luồng cho phép các đối tượng như request
vàg
để có thể truy cập trên toàn cầu qua các chủ đề, trong khi duy trì bối cảnh cụ thể yêu cầu của họ. Hơn nữa, trong khi xử lý một yêu cầu http, Flask có thể mô phỏng các yêu cầu bổ sung từ bên trong, do đó cần phải lưu trữ bối cảnh tương ứng của chúng trên một ngăn xếp. Ngoài ra, Flask cho phép nhiều ứng dụng wsgi chạy cùng nhau trong một quy trình và nhiều hơn một ứng dụng có thể được gọi để hành động trong một yêu cầu (mỗi yêu cầu tạo ra một bối cảnh ứng dụng mới), do đó cần một ngăn xếp ngữ cảnh cho các ứng dụng. Đó là một bản tóm tắt những gì được đề cập trong các câu trả lời trước đó.
Mục tiêu của tôi bây giờ là bổ sung cho sự hiểu biết hiện tại của chúng tôi bằng cách giải thích cách Flask và Werkzeug làm những gì họ làm với những người dân địa phương này. Tôi đã đơn giản hóa mã để tăng cường sự hiểu biết về logic của nó, nhưng nếu bạn có được điều này, bạn sẽ có thể dễ dàng nắm bắt hầu hết những gì trong nguồn thực tế ( werkzeug.local
vàflask.globals
).
Trước tiên hãy hiểu cách Werkzeug thực hiện chủ đề Người địa phương.
Địa phương
Khi một yêu cầu http đến, nó được xử lý trong ngữ cảnh của một luồng. Là một phương tiện thay thế để sinh ra một bối cảnh mới trong một yêu cầu http, Werkzeug cũng cho phép sử dụng greenlets (một loại "micro-thread" nhẹ hơn) thay vì các luồng thông thường. Nếu bạn chưa cài đặt greenlets, nó sẽ trở lại sử dụng các chủ đề thay thế. Mỗi luồng này (hoặc greenlets) có thể được nhận dạng bởi một id duy nhất mà bạn có thể truy xuất bằng get_ident()
chức năng của mô-đun . Chức năng đó là điểm bắt đầu sự kỳ diệu đằng sau có request
, current_app
,url_for
, g
, và các đối tượng toàn cầu bối cảnh bị ràng buộc khác như vậy.
try:
from greenlet import get_ident
except ImportError:
from thread import get_ident
Bây giờ chúng ta có chức năng nhận dạng, chúng ta có thể biết chúng ta đang sử dụng chủ đề nào tại bất kỳ thời điểm nào và chúng ta có thể tạo ra cái được gọi là chủ đề Local
, một đối tượng theo ngữ cảnh có thể được truy cập trên toàn cầu, nhưng khi bạn truy cập các thuộc tính của nó, chúng sẽ giải quyết giá trị của chúng chủ đề cụ thể đó. ví dụ
# globally
local = Local()
# ...
# on thread 1
local.first_name = 'John'
# ...
# on thread 2
local.first_name = 'Debbie'
Cả hai giá trị đều có mặt trên Local
đối tượng có thể truy cập toàn cầu cùng một lúc, nhưng truy cậplocal.first_name
trong ngữ cảnh của luồng 1 sẽ cung cấp cho bạn 'John'
, trong khi nó sẽ trả 'Debbie'
về luồng 2.
Làm thế nào là có thể? Hãy xem xét một số mã (đơn giản hóa):
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()
Từ đoạn mã trên, chúng ta có thể thấy rằng phép thuật sôi sục get_ident()
xác định greenlet hoặc luồng hiện tại. Bộ Local
lưu trữ sau đó chỉ sử dụng như một khóa để lưu trữ bất kỳ dữ liệu theo ngữ cảnh nào cho luồng hiện tại.
Bạn có thể có nhiều Local
đối tượng cho mỗi quá trình và request
, g
, current_app
và những người khác có thể chỉ đơn giản là đã được tạo ra như thế. Nhưng đó không phải là cách nó được thực hiện trong Flask, trong đó những thứ này không phải là kỹ thuật Local
đối tượng , mà là LocalProxy
các đối tượng chính xác hơn . Một là gì LocalProxy
?
LocalProxy
LocalProxy là một đối tượng truy vấn a Local
để tìm một đối tượng quan tâm khác (tức là đối tượng mà nó ủy nhiệm). Hãy xem để hiểu:
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 ...
Bây giờ để tạo proxy có thể truy cập toàn cầu, bạn sẽ làm
# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')
và bây giờ sớm hơn trong quá trình yêu cầu, bạn sẽ lưu trữ một số đối tượng bên trong địa phương mà các proxy được tạo trước đó có thể truy cập, bất kể chúng ta đang sử dụng chủ đề nào
# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()
Ưu điểm của việc sử dụng LocalProxy
như các đối tượng có thể truy cập toàn cầu thay vì tự tạo ra chúng Locals
là nó đơn giản hóa việc quản lý của họ. Bạn chỉ cần một Local
đối tượng duy nhất để tạo ra nhiều proxy có thể truy cập trên toàn cầu. Khi kết thúc yêu cầu, trong khi dọn dẹp, bạn chỉ cần giải phóng cái đó Local
(tức là bạn bật bối cảnh_id khỏi bộ lưu trữ của nó) và không bận tâm đến proxy, chúng vẫn có thể truy cập toàn cầu và vẫn trì hoãn Local
để tìm đối tượng của chúng quan tâm cho các yêu cầu http tiếp theo.
# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()
Để đơn giản hóa việc tạo ra một LocalProxy
khi chúng ta đã có Local
, Werkzeug thực hiện Local.__call__()
phương pháp ma thuật như sau:
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')
Tuy nhiên, nếu bạn nhìn vào nguồn Flask (flask.globals) mà vẫn không phải là cách request
, g
, current_app
vàsession
được tạo ra. Khi chúng tôi đã thiết lập, Flask có thể sinh ra nhiều yêu cầu "giả mạo" (từ một yêu cầu http thực sự duy nhất) và trong quá trình này cũng đẩy nhiều bối cảnh ứng dụng. Đây không phải là trường hợp sử dụng phổ biến, nhưng đó là khả năng của khung. Vì các yêu cầu và ứng dụng "đồng thời" này vẫn bị giới hạn để chạy chỉ với một người có "tiêu điểm" bất cứ lúc nào, nên sử dụng một ngăn xếp cho bối cảnh tương ứng của họ là hợp lý. Bất cứ khi nào một yêu cầu mới được sinh ra hoặc một trong các ứng dụng được gọi, họ sẽ đẩy bối cảnh của họ lên đầu ngăn xếp tương ứng. Flask sử dụng LocalStack
các đối tượng cho mục đích này. Khi họ kết thúc việc kinh doanh của họ, họ bật bối cảnh ra khỏi ngăn xếp.
LocalStack
Đây là một giao LocalStack
diện (một lần nữa mã được đơn giản hóa để tạo điều kiện cho sự hiểu biết về logic của nó).
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
Lưu ý ở trên rằng a LocalStack
là một ngăn xếp được lưu trữ trong một địa phương, không phải là một nhóm các địa phương được lưu trữ trên một ngăn xếp. Điều này ngụ ý rằng mặc dù ngăn xếp có thể truy cập toàn cầu, đó là một ngăn xếp khác nhau trong mỗi luồng.
Flask không có nó request
, current_app
, g
, và session
các đối tượng giải quyết trực tiếp đến một LocalStack
, nó thay vì sử dụng LocalProxy
các đối tượng bọc một chức năng tra cứu (thay vì một Local
đối tượng) sẽ tìm ra đối tượng tiềm ẩn từ 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)
Tất cả những điều này được khai báo khi khởi động ứng dụng, nhưng thực tế không giải quyết bất cứ điều gì cho đến khi bối cảnh yêu cầu hoặc bối cảnh ứng dụng được đẩy đến ngăn xếp tương ứng của chúng.
Nếu bạn tò mò muốn xem bối cảnh thực sự được chèn vào ngăn xếp như thế nào (và sau đó bật ra), hãy xem flask.app.Flask.wsgi_app()
đó là điểm vào của ứng dụng wsgi (tức là những gì máy chủ web gọi và chuyển môi trường http đến khi yêu cầu đến in), và làm theo sự sáng tạo của các RequestContext
đối tượng suốt tiếp theo của nó push()
vào _request_ctx_stack
. Sau khi được đẩy lên trên cùng của ngăn xếp, nó có thể truy cập thông qua _request_ctx_stack.top
. Đây là một số mã viết tắt để thể hiện dòng chảy:
Vì vậy, bạn bắt đầu một ứng dụng và cung cấp nó cho máy chủ WSGI ...
app = Flask(*config, **kwconfig)
# ...
Sau đó, một yêu cầu http xuất hiện và máy chủ WSGI gọi ứng dụng với các thông số thông thường ...
app(environ, start_response) # aka app.__call__(environ, start_response)
Đây là những gì xảy ra trong ứng dụng ...
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()
# ...
và đây là những gì xảy ra với 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()
Giả sử một yêu cầu đã khởi tạo xong, việc tìm kiếm request.path
từ một trong các chức năng xem của bạn sẽ diễn ra như sau:
- bắt đầu từ
LocalProxy
đối tượng truy cập toàn cầu request
.
- để tìm đối tượng quan tâm cơ bản của nó (đối tượng mà nó ủy quyền), nó gọi hàm tra cứu của nó
_find_request()
(chức năng mà nó đã đăng ký làself.local
).
- Hàm đó truy vấn
LocalStack
đối tượng_request_ctx_stack
cho bối cảnh trên cùng trên ngăn xếp.
- để tìm bối cảnh trên cùng,
LocalStack
trước tiên , đối tượng truy vấn Local
thuộc tính bên trong của nó ( self.local
) chostack
tính được lưu trữ trước đó ở đó.
- từ
stack
nó có được bối cảnh hàng đầu
- và
top.request
do đó được giải quyết như là đối tượng quan tâm cơ bản.
- từ đối tượng đó, chúng ta có được
path
thuộc tính
Vì vậy, chúng ta đã thấy làm thế nào Local
, LocalProxy
và LocalStack
làm việc, bây giờ nghĩ về một ý nghĩa và sắc thái trong việc truy xuất path
từ:
- một
request
đối tượng sẽ là một đối tượng đơn giản có thể truy cập toàn cầu.
- một
request
đối tượng sẽ là một địa phương.
- một
request
đối tượng được lưu trữ như một thuộc tính của một địa phương.
- một
request
đối tượng là proxy cho một đối tượng được lưu trữ cục bộ.
- một
request
đối tượng được lưu trữ trên một ngăn xếp, lần lượt được lưu trữ trong một cục bộ.
- một
request
đối tượng là proxy cho một đối tượng trên ngăn xếp được lưu trữ trong một cục bộ. <- đây là những gì Flask làm.