# 上下文管理源码分析

# 上下文涉及到三个类,我们以reqeust为例

  • 首先我们以一个简单的实例为切入点

    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/')
    def hello():
        return 'hello world'
    
    if __name__ == "__main__":
        app.run()
    
  • # Flask的全局Local,该文件在程序启动时就加载了
     # context locals,位于flask.globals文件中,也就是说全局有两个Local对象
    _request_ctx_stack = LocalStack()   #加载全局请求数据,请求上下文
    _app_ctx_stack = LocalStack()    #加载全局应用app,应用上下文
    
    
    current_app = LocalProxy(_find_app) #查找app
    	#全局变量,request导入即可使用,去local中获取ctx,然后去ctx中获取request
    request = LocalProxy(partial(_lookup_req_object, "request")) 
    	 #加载session,方法和上面一致,去local中获取ctx,然后去ctx中获取session,和request机制一样
    session = LocalProxy(partial(_lookup_req_object, "session"))   
    g = LocalProxy(partial(_lookup_app_object, "g"))  #可用于保存全局的参数,给不同的试图函数中
    
     #偏函数使用来指定了,专用来去ctx的request,参数已经给了_lookup_req_object,只需要再次调用偏函数时,就会带有request参数
    
    • 小提示:

      1、_request_ctx_stack 、_app_ctx_stack是上下文数据
      2、request、session、都是可以全局导入使用的额,各自的包含属性不一样,但是源码都是类似的
      3、current_app  用于查找当前app,源码:
          def _find_app():
              top = _app_ctx_stack.top 
              if top is None:
                  raise RuntimeError(_app_ctx_err_msg)
              return top.app #返回存储对象的app属性
      
  • # 启动
    1、app.run()   # 执行run方法,加载的时候程序就执行run函数,触发下面的函数
        
    2、run_simple(host, port, self, **options)函数,从而触发app的__call__方法
    
    3、在__call__函数中调用self.wsgi_app函数,传入的environ是请求的最原始数据,start_response是发送响应的函数
    	return self.wsgi_app(environ, start_response)
    
    4、重点,Flask的所有请求都是通过下面这是几行代码过去的:(可在此定制中间件,在Flask类中)
    	 def wsgi_app(self, environ, start_response):
            ctx = self.request_context(environ) #将请求相关数据environ封装到RequestContext中
            error = None
            try:
                try:
                    ctx.push()  #讲ctx对象封装到local中(每个线程/协程中独立的空间)
                    response = self.full_dispatch_request() # 处理所有视图函数,返回给response,如果你们发生报错就会被下面的异常捕捉
                except Exception as e:  #捕捉异常
                    error = e
                    response = self.handle_exception(e)  #处理异常,返回给response
                except:  # noqa: B001
                    error = sys.exc_info()[1]
                    raise
                return response(environ, start_response)
            finally:  #finally表示最后都要进行处理,即将该协程创建的协程给销毁
                if self.should_ignore_error(error):
                    error = None
                ctx.auto_pop(error)  #最后自动将自己的数据从__storage__中进行删除
    
  • # wsgi_app函数的代码分析
    • ctx = self.request_context(environ) 将请求相关数据environ封装到RequestContext对象中,返回给ctx,其中ctx中包含了flashes、sesson、request等

      1、函数中传入了environ,即所有的请求原始数据,传入了request_context函数中
      
      2、点击request_context函数: #其函数只有下面一句,取实例化一个类并返回实例对象
      	return RequestContext(self, environ)
      
      3、进入RequestContext类,执行__init__函数:
      	def __init__(self, app, environ, request=None, session=None):
              self.app = app  #当前app名称
              if request is None: #默认request必然是None,只传了一个environ
                  request = app.request_class(environ) #会app中找request_class,即回到了Flask类中,找到了:request_class = Request,调用Request类,传入environ参数,最后返回经过封装的request,我们可以调用其中的方法了
                  
              self.request = request  #也就是说ctx可以ctx.app、ctx.request、ctx.session等属性
              self.url_adapter = None
              try:
                  self.url_adapter = app.create_url_adapter(self.request)
              except HTTPException as e:
                  self.request.routing_exception = e
              self.flashes = None  #闪现
              self.session = session
              self._implicit_app_ctx_stack = []
              self.preserved = False
              self._preserved_exc = None
              self._after_request_functions = []
      
    • ctx.push():将封装的请求相关数据的RequestContext对象中的对象,添加到_requset_ctx_stack.push(self)

      1、通过上面返回的ctx,里面除了封装了一些属性外,还封装了方法,比如push
      
      2、在RequestContext类中找到push函数:
      
      	def push(self)  #self就是我们传入的ctx,也就是RequestContext对象
      		top = _request_ctx_stack.top  #top被封装的属性,通过触发__getattr__获取有没有数据
              	if top is not None and top.preserved: #preserved本身为False,用于指示是否保留,之前的数据,如果设置了True,则不会保存之前出错的
                  	top.pop(top._preserved_exc) #用于记录报错异常,出错的会被pop掉
              	app_ctx = _app_ctx_stack.top  #这个和request_ctx_stack类似,,
              	if app_ctx is None or app_ctx.app != self.app:
                  	app_ctx = self.app.app_context()  #如果又=有app,则执行app_context,里面实例化AppContext,返回一个对象
                  	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()
      			
                  #self是requestContext对象,其中包含请求相关的所有数据
              	_request_ctx_stack.push(self)  # 通过分析_request_ctx_stack是LocalStack()的对象,所以应该取LocalStack中找push函数,将app也加入local中的storage中,数据
                  if self.session is None:
                 		session_interface = self.app.session_interface
                  	self.session = session_interface.open_session(self.app,self.request)                  
                  if self.session is None:
                      self.session =session_interface.make_null_session(self.app)
      
              	if self.url_adapter is not None:
                  	self.match_request()
                      
            2.2:触发的top属性:
              @property
              def top(self):
                  """The topmost item on the stack.  If the stack is empty,`None` is returned.
                  """
                  try:
                      return self._local.stack[-1] #. 点触犯__getattr__方法传入stack,取出对象的列表,取-1
                  except (AttributeError, IndexError): #发生异常则return None
                      return None
                      
       3、我们之前说了,在项目启动的时候,我们就运行了_request_ctx_stack = LocalStack(),执行LocalStack的init函数:
          def __init__(self):
              self._local = Local()  # 这个Local就是我们上一章手写的那个Local类似的类
              
       4、点击Local类:
      class Local(object):
          __slots__ = ("__storage__", "__ident_func__")
          
          def __init__(self):
              object.__setattr__(self, "__storage__", {})  #存储所有协程id,以及请求数据,只能这么设置否则迭代过多
              object.__setattr__(self, "__ident_func__", get_ident) #获取id
          ...
          
      5、LocalStack中找push函数:
          def push(self, obj): #注意这里的obj,就是传入的self,即ctx(封装了所有封装好的数据和方法)
              """Pushes a new item to the stack"""
              rv = getattr(self._local, "stack", None) #_local就是Local类的对象,
              if rv is None: # 最开始是空的
                  self._local.stack = rv = []  # .点会触发的是Local的__setattr__函数,赋值给__storage__
              rv.append(obj)
              return rv
          
      6、触发的__setattr__函数:
          def __setattr__(self, name, value):  #name = stack, value = []
              ident = self.__ident_func__() # 查找id
              storage = self.__storage__  
              try:
                  storage[ident][name] = value  #添加值
              except KeyError:
                  storage[ident] = {name: value}
      7、大致的结构:
        _request_ctx_stack.__storage__ = {  #请求上下文
                      唯一标识:{
                            'stack':[ctx,]
                        }
                }   
        _app_ctx_stack.__storage__ = {  #应用上下文
                      唯一标识:{
                            'stack':[app_ctx,]
                        }
                }  
      
    • response = self.full_dispatch_request() 处理所有的视图函数,并获得响应给response

     1、点击进full_dispatch_request()函数:
       def full_dispatch_request(self):
      	self.try_trigger_before_first_request_functions()  #请求进来先处理before_first_request
              try:
                  request_started.send(self) 
                  rv = self.preprocess_request()
                  if rv is None:
                      rv = self.dispatch_request()
              except Exception as e:
                  rv = self.handle_user_exception(e)
              return self.finalize_request(rv)
          
      2、点击try_trigger_before_first_request_functions函数:
          def try_trigger_before_first_request_functions(self):
              if self._got_first_request: #默认配置的_got_first_request=False
                  return
              with self._before_request_lock:  #_before_request_lock=Lock()是把锁
                  if self._got_first_request:return
                  for func in self.before_first_request_funcs: #循环first列表,有就会添加列表中
                      func()
                  self._got_first_request = True
                  
      3、点击preprocess_request函数:
      	def preprocess_request(self):
              bp = _request_ctx_stack.top.request.blueprint # 去获得蓝图的名字,blueprint是属性
      
              funcs = self.url_value_preprocessors.get(None, ())
              if bp is not None and bp in self.url_value_preprocessors:
                  funcs = chain(funcs, self.url_value_preprocessors[bp])
              for func in funcs:
                  func(request.endpoint, request.view_args)
      
              funcs = self.before_request_funcs.get(None, ())
              if bp is not None and bp in self.before_request_funcs:
                  funcs = chain(funcs, self.before_request_funcs[bp])
              for func in funcs:
                  rv = func()
                  if rv is not None:
                      return rv
                
         3.2、点击blueprint函数:
    	@property
          def blueprint(self):#在这里的request
              if self.url_rule and "." in self.url_rule.endpoint:
                  return self.url_rule.endpoint.rsplit(".", 1)[0] #切割
    
    • ctx.auto_pop(error) 调用RequestContext类中的auto_pop函数,error默认为None

      1、点击auto_pop函数,查看源码
          def auto_pop(self, exc):
              if self.request.environ.get("flask._preserve_context") or (
                  exc is not None and self.app.preserve_context_on_exception
              ):
                  self.preserved = True
                  self._preserved_exc = exc
              else:
                  self.pop(exc)  #又执行了本类的pop函数,exc为当前传过来的error
      
      2、点击self.pop,调用_request_ctx_stack进行删除相应的额值,又调回LocalStack类中的pop函数
      	rv = _request_ctx_stack.pop()
          
      3、LocalStack类中的pop函数
          def pop(self):
              stack = getattr(self._local, "stack", None)
              if stack is None:
                  return None
              elif len(stack) == 1:
                  release_local(self._local)
                  return stack[-1]
              else:
                  return stack.pop()
      

# 全局request

上面我们已经对每个请求的数据进行了封装,那么全局中的视图是怎么获取到request?

  • 视图中要使用request,必须导入 from flask import request ,点击查看源码

    1、在全局加载部分,requeset是LocalProxy的对象
    request = LocalProxy(partial(_lookup_req_object, "request"))  #在全局配置中获取的request,偏函数已经给_lookup_req_object传递了request参数
    
  • 点击LocalProxy类,查看源码

    class LocalProxy(object):
     __slots__ = ("__local", "__dict__", "__name__", "__wrapped__")
    
        def __init__(self, local, name=None): #local是传入的函数_lookup_req_object
            object.__setattr__(self, "_LocalProxy__local", local)  #通过_类名__属性名创建一个方法
            object.__setattr__(self, "__name__", name)
            if callable(local) and not hasattr(local, "__release_local__"):
                object.__setattr__(self, "__wrapped__", local)
    
  • 在LocalProxy类中有很多方法,而我们已知在视图函数中使用request属性,下面我 们以 print(request) 为例

    1print(request) #其实触发的时LocalProxy类中的__str__方法:
       print(request.method)  #会触发__getattr__方法,并将method传入
        __str__ = lambda x: str(x._get_current_object())
        
    2、点击查看_get_current_object函数
     def _get_current_object(self):
            if not hasattr(self.__local, "__release_local__"): #最初这里是没有的所有能通过
                return self.__local()  # 执行_lookup_req_object()
            try:
                return getattr(self.__local, self.__name__)  #去__local中获取
            except AttributeError:
                raise RuntimeError("no object bound to %s" % self.__name__) #__name__在init函数中已经设置为了“request”,也就是说获取全局变量中找到当前request对象
    
  • self.__local会触发local函数,即_lookup_req_object 函数

    def _lookup_req_object(name):
        top = _request_ctx_stack.top  #取出stack[-1]对应的值
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        return getattr(top, name) #top是返回的数据RequestContext对象中去获取request属性,也就是Request对象,
    
    	
      @property
      def top(self):
           try:
             return self._local.stack[-1]  # 返回stack[ctx,]
    

# 总结

1、threading.Local 和 Flask 自定义Local对象

2、请求到来到来时
  2.1 ctx = 封装RequestContext(request,session)
  2.2 ctx 放到Local中
  
3、执行视图函数
  3.1  self.full_dispatch_request()函数中进行处理函数
  3.2 如何调用全局request?
  	在程序启动时就加载好了全局request属性、session属性等,request是LocalProxy类的对象,调用request的属性或方法时会触发__str__、__getattr__、__add__等一堆方法,
  3.3 所有的方法都会调用偏函数,_lookup_req_object 来从全局的local中获取request对象,-1是去列表的唯一值
  
4finally中执行ctx.atuo_pop(),删除每次进来的request
# 模拟取属性的过程
from flask.globals import _request_ctx_stack
from functools import partial

def _lookup_req_object(name):
    # name = request
    # top = ctx
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('不存在')
    return getattr(top, name)  # 获取响应的属性

class Foo(object):
    def __init__(self):
        self.xxx = 123
        self.ooo = 88

req = partial(_lookup_req_object,'xxx')
xxx = partial(_lookup_req_object,'ooo') #偏函数
_request_ctx_stack.push(Foo()) #将类添加进去

v = req() #这时候才去取
print(v)
v1=xxx()
print(v1)
_request_ctx_stack.pop() #请求终止,删除响应数据