zoukankan      html  css  js  c++  java
  • Flask之 请求,应用 上下文源码解析

    什么是上下文?

    每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。
    这些值的集合就叫上下文。

    当接受到一个请求时,我们通常将请求封装成一个HttpRequest对象,然后将对象传递给每一个视图函数,我们从request对象中获取相关的请求信息,这样做的缺点就是所有的视图函数都需要添加reqeust参数,即使视图函数中并没有使用到它。

    而在Flask中,将这些信息封装成类似全局变量的东西,视图函数需要的时候,可以使用from flask import request获取。但是这些对象和全局变量不同的是:他们必须是动态的,也就是说在多线程/多协程的情况下,每个线程获取的request都是属于自己的,不会相互干扰。

    引入:

    对于flask而言,它的请求流程和django有着截然不同的流程。在django中,请求是一步一步封装最终传入试图函数中,但是在flask中,视图函数中并没有请求的参数,而是通过上下文的机制完成对请求的解析。

     

     源码解析:

    请求的入口

    对于每次请求进来的时候,都会执行Flask的__all__方法,__call__方法执行wsgi_app方法是对请求处理以及响应的全过程,每次的上下文的创建个销毁都是在其内部实现的,上下文是flask框架的核心。
    
    首先我们要明白,在上下文中,需要完成那些操作:
        1.对原生的请求进行封装,生成视图函数可以操作的request
        2.获取请求头中的cookie信息,生成session 对象。
        3.执行预处理函数和视图函数
        4.返回响应结果

    以下为上下文源码,后续对各部分代码进行分别阐述

        def wsgi_app(self, environ, start_response):
            #environ 请求的原始信息,还没有经过处理
    ctx = self.request_context(environ)#etc = request/session error = None try: try: ctx.push()#将ctx入栈,但是内部也将应用上下文入栈 response = self.full_dispatch_request()#对请求的url进行视图函数的匹配,执行视图函数并且返回响应信息(cookie) except Exception as e:#如果发生错误,就将错误信息作为相应的消息进行返回 error = e response = self.handle_exception(e) except: error = sys.exc_info()[1] raise return response(environ, start_response)#执行响应的消息 finally: if self.should_ignore_error(error): error = None ctx.auto_pop(error)#出栈,删除本次的请求相关信息(拿空间换取时间,释放内存)

    1.请求上下文对象的创建

    ctx = self.request_context(environ) #拿到request和session,即把请求相关的数据都封装到了ctx这个对象中。拿到了请求的原始信息

    生成了RequsetContent类实例,这个类中包含了本次请求的request和session的信息。

        def request_context(self, environ):
                   return RequestContext(self, environ)

    实例化这个类,并且将传入的原生请求信息environ封装到request类的实例红,此时,request1是封装之后的Request实例,session为None

    class RequestContext(object):
       
        def __init__(self, app, environ, request=None):
            self.app = app
            if request is None:
                request = app.request_class(environ)
            self.request = request
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None
    
          
            self._implicit_app_ctx_stack = []
    
           
            self.preserved = False
    
          
            self._preserved_exc = None
    
          
            self._after_request_functions = []
    
            self.match_request()
    request_class = Request

    2.将请求上下文个应用上下文入栈

    #将ctx入栈,但是内部也将应用上下文入栈
    ctx.push()
        def push(self):
     
            top = _request_ctx_stack.top               #获取到的  top == ctx
    if top is not None and top.preserved: top.pop(top._preserved_exc)
    #_app_ctx_stack和_request_ctx_stack都是Local类的实例
    # 获取 应用上下文的栈顶元素,得到 app_ctx app_ctx
    = _app_ctx_stack.top if app_ctx is None or app_ctx.app != self.app:
        # self.app == Fask()
        # 得到 一个 AppContext类的实例对象,得到一个 应用上下文对象 app_ctx,此时 app_ctx拥有以下属性: app_ctx.app = app, app_ctx.g = app.app_ctx_globals_class()
    
                app_ctx = self.app.app_context()
     # 将 app_ctx 入栈,应用上下文入栈 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=LocalStack()={"_local":{"__storage__":{},"__ident__":get_ident}} _request_ctx_stack.push(self)# self = ctx = request,session #执行完push拿到了线程/协程的ID,拿到了name=stack,value = rv =[] # Open the session at the moment that the request context is available. # This allows a custom open_session method to use the request context. # Only open a new session if this is the first time the request was # pushed, otherwise stream_with_context loses the session. if self.session is None:
    # # SecureCookieSessionInterface()
            # session_interface = SecureCookieSessionInterface(),即session_interface就是一个SecureCookieSessionInterface类的实例对象
    
                session_interface = self.app.session_interface
    # 第一次访问:生成一个 字典(容器) 返回至 self.session self.session
    = session_interface.open_session( self.app, self.request ) if self.session is None: self.session = session_interface.make_null_session(self.app)

    首先,应用上下文入栈,它的执行流程去请求上下文相同。

    其次,请求上下文入栈。执行_request_ctx_stack.push(),  先看_request_ctx_stack是什么?由_request_ctx_stack = LocalStack()   

    由此可知,_request_ctx_stack.push()Localstack类的实例对象。进入Localstack的构造方法中;

        def __init__(self):
            self._local = Local()
    即在类实例化过程中,为 _request_ctx_stack 实例对象创建  _local 属性,该属性的值是 
    Local 类实例,进入其构造方法中,在该方法中为每一个 Local 类实例创建 __storage__ 和 __ident_func__ 属性:
    class Local(object):
        __slots__ = ('__storage__', '__ident_func__')
        #__slots__表示给当前对象赋值两个私有属性,有且只有两个
    
        def __init__(self):
            object.__setattr__(self, '__storage__', {})
            object.__setattr__(self, '__ident_func__', get_ident)

    至此,完成了对  _request_ctx_stack 实例对象创建的流程分析,但是需要注意的是,该实例对象并不是在每次请求之后才创建完成的,而是在flask项目启动之后就会被立即创建,该对象对于每次的请求都会调用该对象的push方法进行请求上下文的入栈,也就是说 _request_ctx_stack 是一个单例对象,该单例对象可以在任何的地方被调用,其他的单例对象还有:

    """
        注意:
            在项目启动之后,global里的代码就已经执行完毕,而且也只会执行一次,因此这里面的变量是针对所有请求所使用的,但是根据不同线程id用来存放各自的值
    """
    #  生成 请求上下文栈对象,将请求上下文对象 ctx 保存到 _request_ctx_stack._local.stack = [ctx]中
    _request_ctx_stack = LocalStack()
    # 生成应用上下文栈对象,将应用上下文对象 app_ctx 保存到  _app_ctx_stack._local.stack = [app_ctx]中
    _app_ctx_stack = LocalStack()
     
    # current_app.__local = app
    current_app = LocalProxy(_find_app)
    # 获取ctx.request
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    # 获取 ctx.session
    session = LocalProxy(partial(_lookup_req_object, 'session'))
    # 维护此次请求的一个全局变量,其实就是一个字典
    = LocalProxy(partial(_lookup_app_object, 'g'))

    对于以上的单例对象,在项目启动之后被创建,在项目停止后被销毁,与请求是否进来无任何关系。现在我们知道了 _request_ctx_stack 的创建流程,我们返回之前对请求上下文的入栈操作 _request_ctx_stack.push(self) (self指的是ctx),进入push方法:

    def push(self, obj):   #self = _local   obj = self = ctx = request,session
    """Pushes a new item to the stack"""
    rv = getattr(self._local, 'stack', None)
    if rv is None:
    self._local.stack = rv = [] #.stack相当于执__setattr__方法
    rv.append(obj)
    return rv

    在上述流程中,首先使用反射获取  _request_ctx_stack._local.stack 的值,也就是获取请求栈的值。项目刚启动,在第一次请求进来之前,请求栈的为空,则代码继续向下执行将当前请求的ctx追加至请求栈中,并且返回请求栈的值。这里着重说一下入栈之前的流程和入栈之后的数据结构:执行 self._local.stack = rv = [] ,会调用 Local 类的 __setattr__ 方法

    def __setattr__(self, name, value):#name =stack  value = rv =[]
    ident = self.__ident_func__() #ident 是线程协成的ID
    storage = self.__storage__ #{}
    try:
    storage[ident][name] = value
    except KeyError:
    storage[ident] = {name: value}#{ID:{"stack":[]}}

    self.__ident_func__() 为获取当前此次请求的协程id或者线程id, self.__storage__ 为一个字典对象,在项目启动后的第一个请求进来之后会发生 storage[ident][name] = value 的异常错误,抛出异常被下面捕获,因此执行 storage[ident] = {name: value} (以此次协程id或线程id为key,该key的value为一个字典,在字典中存储一个键值对"stack":[ctx]),即此数据结构为:

    _request_ctx_stack._local.stack={
            线程id或协程id: {
                'stack': [ctx]
            }
    }

    同时, self._local.stack  = [ctx]。至此,完成请求上下文的入栈操作,应用上下文与请求上下文的入栈流程相同,这里不在赘述。至此完成了请求入栈的操作,我们需要知道在上述过程中使用到的四个类: RequestContext (请求上下文类,实例对象ctx中包含了request,Session两个属性)、 Request (对请求的元信息environ进行封装)、 LocalStack (使用该类实例对象 _request_ctx_stack ,维护请求上下文对象ctx的入栈和出栈操作,相当于请求上下文对象的管理者)、 Local (堆栈类,真正存放请求上下文的类),如果你还是对着几个类关系还是不明白,请看我为你准备的图:

     返回 wsgi_app 函数,继续向下执行  response = self.full_dispatch_request() 函数:

    def full_dispatch_request(self):
        #  将 _got_first_request =  True,依次执行定义的 before_first_request 函数
        self.try_trigger_before_first_request_functions()
        try:
            # 触发 request_started 信号
            request_started.send(self)
            #  执行钩子函数:before_request,before_first_request
            rv = self.preprocess_request()
            # 如果执行的before_request,before_first_request函数没有返回值,则继续执行视图函数。若有返回值,则不执行视图函数
            if rv is None:
                # 执行此url对应的别名的视图函数并执行该函数,返回视图函数的返回值,得到相应信息
                rv = self.dispatch_request()
        except Exception as e:
            # 如果发生错误,则将异常信息作为返回值进行返回
            rv = self.handle_user_exception(e)
        # 封装返回信息并返回,包括 session
        return self.finalize_request(rv)

    在函数的内部首先执行预处理函数再执行视图函数,返回预处理函数或视图函数的返回值至浏览器。

    返回  wsgi_app 函数中,继续向下执行  ctx.auto_pop(error) 函数,完成对请求上下文和应用上下文的出栈操作:

    1
    2
    3
    4
    5
    6
    7
    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)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.
     
        .. versionchanged:: 0.9
           Added the `exc` argument.
        """
        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 this interpreter supports clearing the exception information
                # we do that now.  This will only go into effect on Python 2.x,
                # on 3.x it disappears automatically at the end of the exception
                # stack.
                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()
     
            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            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)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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)
            return stack[-1]
        else:
            # 获取并删除列表中的第一个元素,同时返回该元素
            return stack.pop()

    stack获取到的是请求栈或应用栈的列表,栈的长度为1,则进入 elif 控制语句中,首先执行 release_local(self._local) :

    1
    2
    3
    def release_local(local):
         
        local.__release_local__()

     local=self._local ,即执行 Local 类的 __release_local__ 方法,进入该方法:

    1
    2
    3
    def __release_local__(self):
        # 将 self.__storage__ 所维护的字典中删除当前协程或线程id为key的元素
        self.__storage__.pop(self.__ident_func__(), None)

    从上面的语句中可以很明显看出,要执行的操作就是将以当前协程或线程id为key的元素从字典 self.__storage__ 中删除,返回至pop函数中的elif控制语句,最终将列表中的最后一个元素返回。注意,最终 _request_ctx_stack._local 的请求栈和应用栈列表中至少会存在一个元素。

    Flask的请求下文:

    request = LocalProxy(partial(_lookup_req_object, 'request'))

    首先执行了偏函数    _lookup_req_object,  

    def _lookup_req_object(name):
        top = _request_ctx_stack.top
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        return getattr(top, name)
    @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
    执行完偏函数之后,开始执行LocalProxy这个类方法,执行完__init__方法
     def __init__(self, local, name=None):#local =request偏函数
            object.__setattr__(self, '_LocalProxy__local', local)
            #做一个类属性_LocalProxy__local
            object.__setattr__(self, '__name__', name)
            if callable(local) and not hasattr(local, '__release_local__'):
                #callable是否是可执行的
                # "local" is a callable that is not an instance of Local or
                # LocalManager: mark it as a wrapped function.
                object.__setattr__(self, '__wrapped__', local)
    然后开始执行__getattr__方法:拿到需要的
        def __getattr__(self, name):
            if name == '__members__':
                return dir(self._get_current_object())
            return getattr(self._get_current_object(), name)

     上下文流程图:

    上文:::

    下文:::



    OK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


  • 相关阅读:
    nexus
    图片水印
    springmvc+mybatis+spring+redis
    web-fragment模块化使用
    jackson的使用
    httpClient
    ftp上传文件
    windows server 2008 R2中建立ftp站点
    保存网络中的文件
    读取excel文件
  • 原文地址:https://www.cnblogs.com/wqzn/p/10256584.html
Copyright © 2011-2022 走看看