通俗的說,就是一個容器,存儲了一些與程序運行相關的參數變量等,這些內容支持着程序的運行。python
在flask中談論的上下文爲兩個:請求上下文和應用上下文。好比經常使用的g、session、request,屬於請求上下文,其內容只在各自的請求中有效。而current_app就是應用上下文。flask引入應用上下文的概念是爲了更好的支持多應用開發。flask
2.1 繼續從上篇文章falsk是如何處理請求的接着說。上篇文章說到wsgi_app時,提到了調用self.request_context方法時會建立請求上下文對象。segmentfault
def wsgi_app(self, environ, start_response): # 建立當前請求的上下文空間 ctx = self.request_context(environ) error = None try: try: # 將上下文壓入棧中 ctx.push() response = self.full_dispatch_request() except Exception as e: error = e response = self.handle_exception(e) except: # noqa: B001 error = sys.exc_info()[1] raise return response(environ, start_response) finally: if self.should_ignore_error(error): error = None # 將當前上下文從棧空間彈出 ctx.auto_pop(error)
2.2 那就先看一下self.request_context執行了什麼(已刪減部分源碼)。內部首先是實例化了RequestContext這個類(這個名字很明顯吧),實例化方法內部還實例化了request_class這個類(繼承自BaseRequest),返回了請求對象,這個請求對象包含了請求的相關信息。RequestContext類中實現了兩個重要的方法:push和pop方法。看到這裏,依稀明白了請求上下文的處理流程。session
上面wsgi_app中提到的full_dispatch_request方法在處理請求時,會到_request_ctx_stack取棧頂的請求上下文(可繼續看源碼,內容太多,就不貼出來了),對請求處理結束返回相應的響應對象後,再調用auto_pop(內部調用pop)將請求上下文從棧空間彈出。多線程
flask是支持多線程和協程的,好比多線程訪問時,flask是如何保證取請求上下文不會取到同一個呢?併發
def request_context(self, environ): # self即當前app,environ是請求的參數 return RequestContext(self, environ) class RequestContext(object): def __init__(self, app, environ, request=None, session=None): self.app = app if request is None: request = app.request_class(environ) self.request = request self._implicit_app_ctx_stack = [] self.preserved = False def push(self): """Binds the request context to the current context.""" # 取_request_ctx_stack棧頂的請求上下文 top = _request_ctx_stack.top # 若是某次異常,會致使上次請求上下文沒有正常彈出,這裏確保棧頂沒有請求上下文 if top is not None and top.preserved: top.pop(top._preserved_exc) # 取_app_ctx_stack棧頂的應用上下文 app_ctx = _app_ctx_stack.top # 確保當前請求上下文在這個應用上下文內 if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) if hasattr(sys, "exc_clear"): sys.exc_clear() # 將自身壓入_request_ctx_stack棧中 _request_ctx_stack.push(self) def pop(self, exc=_sentinel): 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 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() 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, )
2.3 答案是使用線程或協程的惟一標識,即get_ident這個函數。來看_request_ctx_stack源碼,_request_ctx_stack是個全局變量,一開始實例化Flask類時,就會實例化LocalStack這個類而且導入了這個變量。app
調用push方法時,會觸發self._local的__getattr__方法,若是self._local沒有存儲當前線程或協程的惟一標識,會觸發自身的__setattr__方法,而後將當前請求上下文存儲到這個__storage__屬性中,這樣就保證併發請求時,正確使用對應的上下文啦。ide
_request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() try: from greenlet import getcurrent as get_ident except ImportError: try: from thread import get_ident except ImportError: from _thread import get_ident class Local(object): __slots__ = ("__storage__", "__ident_func__") def __init__(self): object.__setattr__(self, "__storage__", {}) object.__setattr__(self, "__ident_func__", get_ident) def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} class LocalStack(object): def __init__(self): self._local = Local() def push(self, obj): """Pushes a new item to the stack""" # 調用self._local對象中的__getattr__方法 rv = getattr(self._local, "stack", None) if rv is None: # 調用self._local對象中的__setattr__方法,設置當前線程或協程惟一標識 self._local.stack = rv = [] # 壓入棧中當前的請求上下文 # 最終self._local中__storage__的內容相似爲: # {241253254325: {'stack': RequestContext}} rv.append(obj) return rv def pop(self): pass @property def top(self): pass