通俗的说,就是一个容器,存储了一些与程序运行相关的参数变量等,这些内容支持着程序的运行。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