Flask04--flask源码分析

Flask 源码分析

0 flask请求生命周期

# 请求来了,会干什么事?
  app.run()--->会执行 werkzeug库的 run.simple(app)

  最终会执行 app(environ, start_response)
  ---> 对象()   触发类 Flask的 __call__方法
  ---> Flask.wsgi_app(environ, start_response)
  ---> 整个的请求生命周期
    
    
# 代码如下   
def wsgi_app(environ, start_response)
    ctx = self.request_context(environ)   # 详见6.1 ctx上下文分析
    error = None
    try:
        try:
            ctx.push()   # 详见6.2 ctx.push分析 其中:存放ctx到local对象 以及 open_session
            response = self.full_dispatch_request()  # 匹配视图函数执行 其中:请求拓展、信号以及 save_session执行
        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)  # 最后,不管在整个过程中是否出异常,ctx都从local对象上移除 
# 请求生命周期整体执行流程:
-0 flask项目一启动,有6个全局变量
    _request_ctx_stack:LocalStack对象 ---> 封装了local
    _app_ctx_stack :LocalStack对象
    
    request : LocalProxy对象      # 这两个存放在 local的ctx中
    session : LocalProxy对象
    
    current_app : LocalProxy对象  # 这两个存放在 local的app_ctx中
    g : LocalProxy对象
    

-1 请求来了 app.__call__()---->内部执行:self.wsgi_app(environ, start_response)

-2 wsgi_app()
    -2.1 执行:ctx = self.request_context(environ):
         返回一个RequestContext对象,并且封装了request(当次请求的request对象),session
        
    -2.2 执行:ctx.push():RequestContext对象的push方法  同时里面也有session的入口,调用了session的open_session()
        -2.2.1 push方法中中间位置有:_request_ctx_stack.push(self),self是ctx对象
        -2.2.2 去_request_ctx_stack对象的类中找push方法 (LocalStack中找push方法)
        -2.2.3 push方法源码:
            def push(self, obj):
                # 通过反射找self._local,在init实例化的时候生成的:self._local = Local()
                # Local()flask封装的支持线程和协程的local对象
                # 一开始取不到stack,返回None
                rv = getattr(self._local, "stack", None)
                if rv is None:
                    #走到这,self._local.stack=[],rv=self._local.stack
                    self._local.stack = rv = []
                # 把ctx放到了列表中
                # self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
                rv.append(obj)
                return rv

-3 如果在视图函数中使用request对象,比如:print(request)
    -3.1 会调用request对象的__str__方法,request类是:LocalProxy
    -3.2 LocalProxy中的__str__方法:lambda x: str(x._get_current_object())
        -3.2.1 内部执行self._get_current_object()
        -3.2.2 _get_current_object()方法的源码如下:
            def _get_current_object(self):
                if not hasattr(self.__local, "__release_local__"):
                    # self.__local()  在init的时候,实例化的,在init中:object.__setattr__(self, "_LocalProxy__local", local)
                    # 用了隐藏属性
                    # self.__local 实例化该类的时候传入的local(偏函数的内存地址:partial(_lookup_req_object, "request"))
                    # 加括号返回,就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
                    # 这个地方的返回值就是request对象(当此请求的request,没有乱)
                    return self.__local()
                try:
                    return getattr(self.__local, self.__name__)
                except AttributeError:
                    raise RuntimeError("no object bound to %s" % self.__name__)

        -3.2.3 _lookup_req_object函数源码如下:
            def _lookup_req_object(name):
                # name是'request'字符串
                # top方法是把第二步中放入的ctx取出来,因为都在一个线程内,当前取到的就是当次请求的ctx对象
                top = _request_ctx_stack.top
                if top is None:
                    raise RuntimeError(_request_ctx_err_msg)
                # 通过反射,去ctx中把request对象返回
                return getattr(top, name)
        -3.2.4 所以:print(request) 实质上是在打印当此请求的request对象的__str__

-4 如果在视图函数中使用request对象,比如:print(request.method):实质上是取到当次请求的reuquest对象的method属性

-5 最终,请求结束执行: ctx.auto_pop(error),把ctx移除掉

1 ctx生成源码分析

# 请求生命周期的第一行:ctx叫请求上下文
ctx = self.request_context(environ)

---> Flask.request_context(environ)
---> RequestContext(self, environ)  
     # 实例化得到了一个RequestContext类的对象  此时self 是Flask类对象 app

---> RequestContext.init方法中
    def __init__(self, app, environ, request=None, session=None):
        self.app = app
        if request is None: # 创造出一个请求对象
            request = app.request_class(environ)
        self.request = request  # 封装了request(当次请求的request对象)
        self.flashes = None
        self.session = session
        
# 总结:
  每一次请求,都会生成 当前请求 的 ctx上下文对象
  包含着当次请求的request对象,app对象,flashes和session对象   

2 ctx.push()源码分析

# 执行:ctx.push()  
  把每个请求的ctx对象放到了local对象中,来一个请求就放一次
  同时里面也有session的入口,调用了session的open_session()
  也有 应用上下文 app_ctx 的入口 :生成和存放到local中
  # local处理了并发安全,所以自己放的都是自己的,相互不影响
  
---> RequestContext对象的push方法  

1 push方法中 中间位置有:_request_ctx_stack.push(self),self是ctx对象

2 _request_ctx_stack 是全局变量 是LocalStack 类的 push方法

3 LocalStack类的push方法源码:
    def push(self, obj):
        # 通过反射找self._local,在LocalStack类的init实例化的时候生成的:self._local = Local()
        # Local()是 flask封装的支持线程和协程的local对象
        # 一开始取不到stack,返回None
        rv = getattr(self._local, "stack", None)
        if rv is None:
            # 走到这 初始化每个线程 {'stack':[]}
            # self._local={'线程id1':{'stack':[]},'线程id2':{'stack':[]},'线程id3':{'stack':[]}}
            self._local.stack = rv = []
        # 把ctx放到了列表中
        # self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
        rv.append(obj)
        return rv

3 request对象源码分析

# 引入:Request为全局对象,怎么保证不同请求不会数据错乱
  每次请求访问,都会生成各自线程的ctx对象,其中包括:各自线程的request、session等对象
    
  # 怎么保证 不同请求访问,拿到的是各自的request对象尼?
    将各自线程的ctx对象,放进了一个Local对象里
      
  # python的Local对象   类似于 java中的 threadlocal
    多个线程可以同时读写这个变量,并且不会有并发安全的问题
    
    不同线程操作的是自己的数据  本质是大字典  {'key值为线程id号': 'value值为各自线程存放的数据'}

    l=local() 
    # 线程1 
    l.name='lqz'

    # 线程2
    l.name='egon'



##### 具体源码解读:

# 如果在视图函数中使用request对象,比如:print(request)
1 会调用request对象的__str__方法,request类是:LocalProxy

2 LocalProxy类中的__str__方法:lambda x: str(x._get_current_object())
    -2.1 内部执行self._get_current_object()
    -2.2 _get_current_object()方法的源码如下:
        def _get_current_object(self):
            if not hasattr(self.__local, "__release_local__"):
                # self.__local() 用了隐藏属性,外部调用的话,需要变形:'_类名__local'
                # 故是在LocalProxy类的init的时候实例化的 :object.__setattr__(self, "_LocalProxy__local", local)
                # 即:self.__local = local,local又是实例化该类的时候 传入的 偏函数的内存地址:
                #    partial(_lookup_req_object, "request")
                    
                # local() 加括号 实例化调用 就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
                # 故 返回值就是request对象 (当此请求的request,没有乱)
                return self.__local()
            try:
                return getattr(self.__local, self.__name__)
            except AttributeError:
                raise RuntimeError("no object bound to %s" % self.__name__)

    -2.3 _lookup_req_object函数源码如下:
        def _lookup_req_object(name):
            # name是'request'字符串
            # _request_ctx_stack 是全局变量  是LocalStack 类的 top方法
            # top方法中:stack = getattr(self._local, "stack", None)  就是去local对象中,获取stack的值
            # 因为都在一个线程内,其实就是第二步中放入的ctx取出来 取到的就是当次请求的ctx对象
            top = _request_ctx_stack.top
            if top is None:
                raise RuntimeError(_request_ctx_err_msg)
            # 通过反射,去ctx中把request对象返回
            return getattr(top, name)
        
    -2.4 所以:print(request) 实质上是在打印当此请求的request对象的__str__

    
# 如果在视图函数中使用request对象,比如:print(request.method): 
  实质上是取到当次请求的reuquest对象的method属性

4 其他源码分析

# 1 session:
    -请求来了open_session
        ctx.push()  # 入口
        --->也就是RequestContext类的push方法的最后位置:
            if self.session is None:
                # self是ctx,ctx中有个app就是flask对象
                # self.app.session_interface也就是:SecureCookieSessionInterface()
                session_interface = self.app.session_interface
                self.session = session_interface.open_session(self.app, self.request)
                if self.session is None:
                    # 经过上面还是None的话,就生成了一个空session
                    self.session = session_interface.make_null_session(self.app)

    -请求走了save_session 
        response = self.full_dispatch_request()   # 入口
        # 方法内部:执行了before_first_request,before_request,视图函数,after_request,save_session
        
        self.full_dispatch_request()
        ---> 执行:self.finalize_request(rv)
        ---> 执行:self.process_response(response)
        ---> 最后:self.session_interface.save_session(self, ctx.session, response)
            
            
# 2 请求扩展相关  
    response = self.full_dispatch_request()   # 入口
    ---> before_first_request,before_request,after_request依次执行
        
        
# 3 信号的触发
    信号名.send()  # 其中各种信号的 send方法 就是在触发执行
    response = self.full_dispatch_request()   # 入口
        
        
# 4 flask有一个请求上下文,一个应用上下文
    -ctx:
        -是RequestContext对象: 封装了request和session
        -调用 _request_ctx_stack.push(self)就是把:ctx放到了local对象里

    -app_ctx:
        -是AppContext对象: 封装了当前的app和g
        -调用 _app_ctx_stack.push(self) 就是把:app_ctx放到了local对象里
                
		
# 5 代理模式
    -request和session就是代理对象,用的就是代理模式 在原来的对象上加上格外的处理 返回出去新的代理对象
    
    
# 6 g是个什么鬼?  g的全称的为global 
    专门用来存储用户信息的g对象,是一个全局变量,存取当次请求中的数据
    g对象在同一次请求中,所有代码的位置,都是可以使用的 (当次请求中传递一些数据)
    
    类似于 django的 request.context
    
    # eg: 
      某个视图请求,需要经过五个函数处理,正常来说,是需要一层层的作为参数传递
      但存放在g对象,其他函数位置 可直接g对象获取
    
    
# 7 g对象和session的区别
	g对象只对当次请求有效(当此请求内有效)
    session:可以跨请求,该用户的多次请求中都可以使用
posted @ 2022-08-09 12:32  Edmond辉仔  阅读(109)  评论(0)    收藏  举报