一、什么是上下文(context)
程序中上下文的概念网上解释很多,大概意思也都差不多,我感觉就是程序运行环境的状态、变量的集合,上下文对象就是把这些东西封装在一起以便程序运行过程中更方便地去调用修改一些参数吧。这里摘一段网友回复:
每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。
Flask中有2个上下文对象,AppContext(应用上下文)和RequestContext(请求上下文),应用上下文封装了一些应用相关的数据(如:app,g),请求上下文封装了一些请求相关的数据(如:request,session)。
二、请求到来处理流程
当每一个请求到来的时候,都会实例化一个AppContext(应用上下文)对象和RequestContext(请求上下文)对象,然后将两个对象分别放到两个栈的栈顶,两个栈分别叫_app_ctx_stack和_request_ctx_stack,这两个栈都是同一种类型LocalStack(该类型栈具有线程隔离的特性,用以区分不同请求实例化的上下文对象),这两个栈是在Flask对象初始化的时候生成的。当处理完请求后,这两个上下文对象会被从栈顶删除。
三、源码基本实现
先app.run()运行程序,等待请求到来。请求到来后调用app的__call__()方法,调用wsgi_app函数处理响应请求。 Flask是符合WSGI标准的框架,WSGI主要分两部分,server和application两部分,server负责接受客户端请求并进行解析,然后将其传入application,客户端处理请求并将响应头和正文返回服务器。上面的 wsgi_app(self, environ, start_response): 就是一个符合WSGI协议的应用程序。environ, start_response 这两个参数是协议中规定的。
from flask import Flask app = Flask(__name__) if __name__ == "__main__": app.run() #首先app.run() 启动程序,调用werkzeug的run_simple方法,开始监听请求 #当请求到来,app被调用,调用app对象的__call__()方法,调用self.wsgi_app //--app.py def __call__(self, environ, start_response): """The WSGI server calls the Flask application object as the WSGI application. This calls :meth:`wsgi_app` which can be wrapped to applying middleware.""" return self.wsgi_app(environ, start_response) //--app.py def wsgi_app(self, environ, start_response): #这是一个符合WSGI协议的应用程序 ctx = self.request_context(environ) """ 实例化一个RequestContext对象,这个对象主要有两个方法push和pop,入栈和出栈,并且入栈的时候会 先判断_app_ctx_stack中是否有AppContext对象,如果没有先实例化一个AppContext对象并且入栈 """ error = None try: try: ctx.push() #执行入栈操作、还有对session的操作 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) #请求响应完成后把RequestContext和AppContext从栈顶删除
四、栈_app_ctx_stack和_request_ctx_stack如何实现线程隔离
if __name__ == "__main__": app.run(threaded = True) 开启多线程当Flask开启多线程时,每个请求都会实例化AppContext(应用上下文)和RequestContext(请求上下文)对象,由于对象的名字是一样的,所以需要做线程的隔离。
先看下栈的结构,看下ctx.push()入栈,入的是哪个栈。
//--app.py def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) error = None try: try: ctx.push() """ ctx是个RequestContext对象,定义在ctx.py文件中。查看它的push方法。 """ //--ctx.py class RequestContext(object): #省略中间代码 def push(self): top = _request_ctx_stack.top if top is not None and top.preserved: top.pop(top._preserved_exc) # 请求上下文入栈前先确认是否已经有应用上下文在栈中,没有的话先把应用上下文入栈 app_ctx = _app_ctx_stack.top if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() #实例化一个AppContext对象 app_ctx.push() #应用上下文入栈,查看AppContext的push方法看到栈为_app_ctx_stack 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.push(self) #self就是RequestContext实例本身,入的栈叫_request_ctx_stack由上面代码可以看出,入的栈为_request_ctx_stack 和_app_ctx_stack 。这两个栈定义在globals.py中。是LocalStack()对象。 LocalStack()对象是封装了Local()对象,Local对象是可以实现线程隔离的,主要利用线程的id作为key来区分。LocalStack把Local封装成一个栈对象,定义了栈的常用操作方法,push、pop、top。
//-- globals.py _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) request = LocalProxy(partial(_lookup_req_object, "request")) session = LocalProxy(partial(_lookup_req_object, "session")) g = LocalProxy(partial(_lookup_app_object, "g"))Local()对象
主要看初始化方法__init__ ()和重写的设置属性方法__setattr__(),获取属性方法__getattr__()。主要思想就是用线程的id当作字典的key,区分不同线程的相同变量的值。
//-- werkzeug/local.py 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) #定义一个方法,获取当前协程或线程的ID,get_ident方法在上面导入 def __iter__(self):... def __call__(self, proxy):... def __release_local__(self):... #省略中间代码 def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] #先通过id筛选 except KeyError: raise AttributeError(name) def __setattr__(self, name, value): #重写属性设置方法__setattr__, ident = self.__ident_func__() #获取线程id,假设id = 1111 storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} #赋值, storage={1111:{name:value}} ''' 重写了设置属性方法__setattr__, 例: obj = Local() obj.x = 99 实际效果是保存在__storage__字典中,__storage__={1111:{x:99}} ,假设1111是当前线程id; 如果有多个同时设置属性时: obj1.x = 97 线程id:1111 obj2.x = 98 线程id:2222 obj3.x = 99 线程id:3333 ==》__storage__={1111:{x:97},2222:{x:98},3333:{x:98}} 用线程id来区分不同线程的相同变量x的值 ''' def __delattr__(self, name): try: del self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name)LocalStack()对象
把Local对象封装成一个栈对象
//-- werkzeug/local.py class LocalStack(object): def __init__(self): self._local = Local() #实例化一个Local对象 def __release_local__(self):... @property def __ident_func__(self):... @__ident_func__.setter def __ident_func__(self, value): ... def __call__(self):... def push(self, obj): """Pushes a new item to the stack""" rv = getattr(self._local, "stack", None) #获取stack属性,没有返回None if rv is None: self._local.stack = rv = [] """ self._local.stack=[] 设置属性值,调用Local的__setattr__方法, 生成 __storage__ = {1111:{'stack':[]}} 同时rv也指向这个空列表 """ rv.append(obj) #把对象加入栈 __storage__ = {1111:{'stack':[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) #删除__storage__字典中对应线程id的数据 return stack[-1] #-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