Flask上下文管理机制流程(源码剖析)

  • 2019 年 10 月 11 日
  • 筆記

Flask请求上下文管理

1 偏函数

  • partial 使用该方式可以生成一个新函数

    from functools import partial    def mod( n, m ):    return n % m    mod_by_100 = partial( mod, 100 )  # 100传给n    print mod( 100, 7 )  # 2  print mod_by_100( 7 )  # 2

2 线程安全

import time  from threading import local    class Foo(local):  # 继承local,保证线程安全,也保证了处理速度,threading.local()这个方法的特点用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问,      num = 0    foo = Foo()  def add(i):      foo.num =i      time.sleep(0.5)      print(foo.num)    from threading import Thread  for i in range(20):      task = Thread(target=add,args=(i,))      task.start()      

3 请求上下文

3.1 Flask请求上文

  1. 当请求进来时,app(), Flask实例化对象app**执行__call__**
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)
  1. 执行wsgi_app 得到 一个 RequestContext的对象 ctx (封装了request以及session)

    ctx = self.request_context(environ)
    class RequestContext(object):      #此时的self是RequestContext对象 -->ctx 中封装了request/session      def __init__(self, app, environ, request=None):          self.app = app      #app = Flask对象          if request is None:              #请求的原始信息通过request_class后此时request已经存在,request.methods等              request = app.request_class(environ)          self.request = request          self.url_adapter = app.create_url_adapter(self.request)          self.flashes = None          self.session = None
  2. ctx 执行 ctx.push():

    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)
  3. RequestContext对象的push方法

    def push(self):          # _request_ctx_stack = LocalStack()一个LocalStack对象          # _request_ctx_stack._local = LocalStack()._loacl = {"__storage__":{},"__ident_func__":get_ident}          top = _request_ctx_stack.top          #top =None          if top is not None and top.preserved:              top.pop(top._preserved_exc)  
    • _ request_ctx_stack是一个LocalStack对象 ,LocalStack()._local是一个Local对象 即Local()
    class LocalStack(object):      def __init__(self):          self._local = Local()          #self._loacl = {"__storage__":{},"__ident_func__":get_ident}
    • _request_ctx_stack中top方法,返回None (想哭,但是没有眼泪,这个方法,琢磨了半个小时)
  4. Local对象经过初始化得到的字典值

    class Local(object):      #限定键槽,当前只能由两个属性值__storage__,__ident_func__      __slots__ = ('__storage__', '__ident_func__')        def __init__(self):          object.__setattr__(self, '__storage__', {})          object.__setattr__(self, '__ident_func__', get_ident)            # {"__storage__":{},"__ident_func__":get_ident}  #此时get_dient 是个没有执行的函数,内存地址
    • _request_ctx_stack中top方法,返回None (第二次不会上当)
    @property      def top(self):          """The topmost item on the stack.  If the stack is empty,          `None` is returned.          """          try:              # self._local 即Local对象调用__getattr__方法              #在下文时候Local对象{"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}              # [ctx->request/session]              return self._local.stack[-1]              #得到ctx对象          except (AttributeError, IndexError):              return None
  5. _request_ctx_stack 对象执行push方法

    _request_ctx_stack.push(self)  #当前的self为ctx
    def push(self, obj):          #此时的self是LocalStack对象, obj为ctx          """Pushes a new item to the stack"""          # self._local = {"__storage__":{},"__ident_func__":get_ident}          #找不到返回值是None          rv = getattr(self._local, 'stack', None)          if rv is None:              #由于.stack后面有等号,执行的时候Local()对象的__setattr__方法              #实际上是地址的赋值,此时stack和rv都指向空列表              self._local.stack = rv = []              #{"__storage__":{8080:{stack:rv=[]}},"__ident_func__":get_ident}          rv.append(obj)          # {"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}          # 应用上下文时候{"__storage__":{8080:{stack:rv=[app_ctx->(app/g)]}},"__ident_func__":get_ident}          return rv          #rv=[ctx->request/session]
    def __setattr__(self, name, value):          #name=stack   value=rv=[]          #self是Local对象 {"__storage__":{},"__ident_func__":get_ident}          ident = self.__ident_func__() #执行get_ident函数获取当前线程id 8080          storage = self.__storage__  #storge ={8080:{stack:rv=[]}}          try:              storage[ident][name] = value          except KeyError:              storage[ident] = {name: value}   #storage={}             # {"__storage__":{8080:{stack:rv=[]}},"__ident_func__":get_ident}
    • 执行完push方法 请求上文结束:
    #当请求进来,第一件事就是要把当前这个请求在我服务器上的线程开辟一个空间(线程对应的空间,必须含有stack对应一个列表存放ctx(request/session)  # {"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}

3.3.2Flask请求下文

  1. 导入request开始使用,在request中

    #此时request是一个函数包裹一个偏函数   LocalProxy()是一个代理  #当前的request是一个LocalProxy()  request.method  执行__getattr__方法  request = LocalProxy(      partial(_lookup_req_object, 'request')   #return request对象  )
  2. 在偏函数中 将request传入到 _lookup_req_object中: 此时得到一个request对象

    def _lookup_req_object(name):      # _request_ctx_stack是LocalStack对象      top = _request_ctx_stack.top      #下文[ctx->request/session]      if top is None:          raise RuntimeError(_request_ctx_err_msg)      #此时的name是request,从ctx对象中找出request对象      return getattr(top, name)

    @property      def top(self):          """The topmost item on the stack.  If the stack is empty,          `None` is returned.          """          try:              # self._local 即Local对象调用__getattr__方法              #在下文时候Local对象{"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}              # [ctx->request/session]              return self._local.stack[-1]              #得到ctx对象          except (AttributeError, IndexError):              return None
    • 此时的top不是None已经存在值 (0.0)
  3. partial(_lookup_req_object, ‘request’) 这一层执行完得到一个reauest对象,将偏函数传入到LocalProxy中

    @implements_bool  class LocalProxy(object):      __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')        def __init__(self, local, name=None):          #local是request偏函数          object.__setattr__(self, '_LocalProxy__local', local)   #__local = request偏函数          object.__setattr__(self, '__name__', name)          #当前偏函数可以执行而且判断loacl中是否有 __release_local__  ==>这句话成立          if callable(local) and not hasattr(local, '__release_local__'):              # "local" is a callable that is not an instance of Local or              # LocalManager: mark it as a wrapped function.              object.__setattr__(self, '__wrapped__', local)  #__warpped__还是local偏函数
  4. 当前的request是一个LocalProxy() request.method 执行LocalProxy中的__getattr__方法

        def __getattr__(self, name): # name是method(举例)          if name == '__members__':              return dir(self._get_current_object())          #此时self._get_current_object()是经过_local 执行后得到的request对象,从request对象中去取出method          return getattr(self._get_current_object(), name)

    def _get_current_object(self):          #self._local是偏函数          if not hasattr(self.__local, '__release_local__'):              #执行偏函数,返回request对象              return self.__local()          try:              return getattr(self.__local, self.__name__)          except AttributeError:              raise RuntimeError('no object bound to %s' % self.__name__)

3.3.3小结

由此看来,falsk上下文管理可以分为三个阶段:

  1. 请求上文 ->
    当请求进来,第一件事就是要把当前这个请求在服务器上的线程开辟一个空间(线程对应的空间,必须含有stack对应一个列表存放ctx(request/session),具体–>:将request,session封装在 RequestContext类中
    app,g封装在AppContext类中,并通过LocalStack将requestcontext和appcontext放入Local类中
    在local类中,以线程ID号作为key的字典,
  2. 请求下文:
    通过localproxy—>偏函数—>localstack—>local取值
  3. ‘请求响应时’:–>要将上下文管理中的数据清除
    先执行save.session()再各自执行pop(),将local中的数据清除

详细看源码

3.4 应用上下文

  • 执行wsgi_app方法

       #ctx为一个RequestContext的对象,参数为environ          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)
  • 执行push方法,_app_ctx_stack 同样是LocalStck对象,初始化时候top为None

        def push(self):          app_ctx = _app_ctx_stack.top          #app_ctx = None          if app_ctx is None or app_ctx.app != self.app:              app_ctx = self.app.app_context()   #app_context是AppContext对象  与RequestContenx一样,知识序列化出app和g              app_ctx.push()              # 应用上文时候{"__storage__":{8080:{stack:rv=[app_ctx->(app/g)]}},"__ident_func__":get_ident}              self._implicit_app_ctx_stack.append(app_ctx)          else:              self._implicit_app_ctx_stack.append(None)
    • 执行app_ctx.push 进而 **_app_ctx_stack.push**
       def push(self):          """Binds the app context to the current context."""          self._refcnt += 1          if hasattr(sys, 'exc_clear'):              sys.exc_clear()              #将AppContext存在LocalStack对象中          _app_ctx_stack.push(self)          appcontext_pushed.send(self.app)
        def push(self, obj):          #此时的self是LocalStack对象, obj为ctx          """Pushes a new item to the stack"""          # self._local = {"__storage__":{},"__ident_func__":get_ident}          #找不到返回值是None          rv = getattr(self._local, 'stack', None)          if rv is None:              #由于.stack后面有等号,执行的时候Local()对象的__setattr__方法              #实际上是地址的赋值,此时stack和rv都指向改空列表              self._local.stack = rv = []              #{"__storage__":{8080:{stack:rv=[]}},"__ident_func__":get_ident}          rv.append(obj)          # {"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}          # 应用上下文时候{"__storage__":{8080:{stack:rv=[app_ctx->(app/g)]}},"__ident_func__":get_ident}          return rv          #rv=[ctx->request/session]

    到此,push完毕,应用上文结束,应用下文在离线脚本时候使用,另外:在global.py中

def _find_app():      top = _app_ctx_stack.top    #得到app_ctx(app / g)      if top is None:          raise RuntimeError(_app_ctx_err_msg)      return top.app  #返回一个app即flask对象 只不过此时的flask对象 是公共的,与初始化的相同      # 但是是独立出来已经被配置好的Flask对象    # LocalStack是 针对当前这个线程对独立的Flask_app进行修改, 不影响现在运行的app  =>离线脚本  #但是这个app 在请求结束后会从LocalStack中通过 __delattr__ 删除      # context locals  _request_ctx_stack = LocalStack()  #LocalStark = self._loacl = {"__storage__":{},"__ident_func__":get_ident}  _app_ctx_stack = LocalStack()  current_app = LocalProxy(_find_app)   # current_app可以点 .run |  .route 等