이전 답변은 이미 요청하는 동안 플라스크 배경에서 진행되는 작업에 대한 훌륭한 개요를 제공합니다. 아직 읽지 않았다면 이것을 읽기 전에 @MarkHildreth의 답변을 권장합니다. 요컨대, 각각의 http 요청에 대해 새로운 컨텍스트 (스레드)가 생성 Local
되므로 request
and와 같은 객체를 허용 하는 스레드 기능이 필요합니다.g
요청 특정 컨텍스트를 유지하면서 스레드 전체에서 액세스 할 수 있습니다. 또한 http 요청을 처리하는 동안 Flask는 내부에서 추가 요청을 에뮬레이트 할 수 있으므로 해당 컨텍스트를 스택에 저장해야합니다. 또한 Flask를 사용하면 단일 프로세스 내에서 여러 wsgi 응용 프로그램을 서로 실행할 수 있으며 요청하는 동안 하나 이상의 작업을 호출하여 호출 할 수 있습니다 (각 요청은 새로운 응용 프로그램 컨텍스트를 생성 함). 따라서 응용 프로그램에 대한 컨텍스트 스택이 필요합니다. 그것은 이전 답변에서 다룬 내용에 대한 요약입니다.
내 목표는 이제 설명하여 우리의 현재 이해를 보완하는 방법 플라스크와 WERKZEUG들이 이러한 상황에 맞는 지역 주민과 함께해야합니까 것. 논리를 더 잘 이해하기 위해 코드를 단순화했지만이 정보를 얻으면 실제 소스 ( werkzeug.local
및 flask.globals
) 에있는 대부분의 내용을 쉽게 파악할 수 있습니다 .
Werkzeug가 스레드 로컬을 구현하는 방법을 먼저 이해합시다.
현지
http 요청이 들어 오면 단일 스레드 컨텍스트 내에서 처리됩니다. http 요청 중에 새로운 컨텍스트를 생성하기위한 대안으로 Werkzeug는 일반 스레드 대신 그린 릿 (일부 더 가벼운 "마이크로 스레드")을 사용할 수 있습니다. Greenlet이 설치되어 있지 않으면 대신 스레드 사용으로 되돌아갑니다. 이 스레드 (또는 그린 릿) 각각은 고유 한 ID로 식별 할 수 있으며 모듈의 get_ident()
기능으로 검색 할 수 있습니다 . 그 기능을 가진 뒤에 마법의 시작 지점입니다 request
, current_app
, url_for
, g
, 및 기타 상황에 바인딩 전역 개체.
try:
from greenlet import get_ident
except ImportError:
from thread import get_ident
이제 우리는 identity 함수를 사용하여 주어진 시간에 어떤 스레드를 사용하고 있는지를 알 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
및 다른 사람은 간단하게 그렇게 만들 수 있었다. 그러나 이것이 기술적으로 Local
객체가 아니라보다 정확하게 LocalProxy
객체 인 Flask에서 수행 된 방식은 아닙니다 . 무엇입니까 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
전역 적으로 액세스 가능한 많은 프록시를 만들려면 단일 개체 만 있으면 됩니다. 요청이 끝나면 정리하는 동안 단순히 하나를 해제하고 Local
(즉, 저장소에서 context_id를 팝) 프록시에 신경 쓰지 않고 여전히 전역 적으로 액세스 할 수 있으며 여전히 Local
객체를 찾기 위해 연기 합니다. 후속 http 요청에 관심이 있습니다.
# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()
Werkzeug는 이미을 LocalProxy
가지고있을 때 의 생성을 단순화하기 위해 다음과 같이 마술 방법을 Local
구현합니다 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 요청에서 여러 개의 "가짜"요청을 생성 할 수 있으며 그 과정에서 여러 응용 프로그램 컨텍스트를 푸시 할 수도 있습니다. 이것은 일반적인 사용 사례는 아니지만 프레임 워크의 기능입니다. 이러한 "동시"요청 및 앱은 여전히 "초점"을 가진 하나만 실행되도록 제한되기 때문에 각각의 컨텍스트에 스택을 사용하는 것이 좋습니다. 새 요청이 생성되거나 응용 프로그램 중 하나가 호출 될 때마다 해당 스택의 맨 위에 컨텍스트를 푸시합니다. 플라스크는 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
A를 직접 해결하는 객체 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 앱의 시작 지점 (즉, 웹 서버가 호출하고 http 환경을 전달할 대상)을 확인하십시오. 요청)에 제공하고,의 생성에 따라 RequestContext
모든 후속 통해 객체를 push()
에 _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
뷰 함수 중 하나 에서 조회가 다음과 같이 진행됩니다.
- 전역 적으로 접근 가능한
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가하는 일입니다.