Flask 源碼流程,上下文管理

 源碼流程

建立對象

from flask import Flask 

"""
1 實例化對象 app 
""" 
app = Flask(__name__)

"""
2 設置路由
    將路由關係放在 app.url_map = {} 中
"""
@app.route("/index")
def index():
    return "index"

if__name__ == "__main__":
"""
3 啓動socket服務端 
"""
    app.run()

"""
4 用戶請求到來就會執行 __call__ 方法 
"""

run

# 啓動入口簡約版代碼  
from werkzeug.wrappers import Response
from werkzeug.serving import run_simple

class Flask(object):
    def __call__(self,environ, start_response):
        response = Response('hello')
        return response(environ, start_response)

    def run(self):
        run_simple('127.0.0.1', 8000, self)

run_simple(host,port,self,**options)
會對第三個傳入的參數加()進行執行
第三個參數若是是app對象就執行其 __call__ 方法python

__call__ 

def __call__(self,environ, start_response):
    # environ 請求相關的全部數據 第一手的數據,由wsgi進行的初步封裝
    # start_response 用於設置響應相關數據 
    return wsgi_app(environ,start_response)

 call 返回的是 wsgi_app 的執行結果web

wsgi_app

wsgi_app 裏面作的事情就不少了。面試

咱們一步一步來看redis

第一步 ctx 封裝

首先把請求相關的全部數據都 封裝了到 一個 ctx 對象sql

並且經過 __init__ 能夠看到 封裝了 request 以及建立了一個 空的 session 數據庫

第一步總結

  獲得了 ctx 對象django

  建立ctx.request 以及 ctx.session = None flask

第二步 push

 

 來看下 push 都作了什麼cookie

 

 封了一個top,這是啥不知道,要繼續看是個 LocalStack 對象session

 

class LocalStack(object):

    """This class works similar to a :class:`Local` but keeps a stack
    of objects instead.  This is best explained with an example::

        >>> ls = LocalStack()
        >>> ls.push(42)
        >>> ls.top
        42
        >>> ls.push(23)
        >>> ls.top
        23
        >>> ls.pop()
        23
        >>> ls.top
        42

    They can be force released by using a :class:`LocalManager` or with
    the :func:`release_local` function but the correct way is to pop the
    item from the stack after using.  When the stack is empty it will
    no longer be bound to the current context (and as such released).

    By calling the stack without arguments it returns a proxy that resolves to
    the topmost item on the stack.

    .. versionadded:: 0.6.1
    """

    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def _get__ident_func__(self):
        return self._local.__ident_func__

    def _set__ident_func__(self, value):
        object.__setattr__(self._local, '__ident_func__', value)
    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__

    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
            return rv
        return LocalProxy(_lookup)

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(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)
            return stack[-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
LocalStack 源碼

這個 LocalStack 對象是什麼咱們也仍是不知道,還需須要繼續看,發現實際上是個 Local 對象

 看到Local 到這裏發現應該是看到底了,這樣稍微看下 local 對象就是在 __storage__ 裏面存了個數據結構

這個數據結構是這樣的,

__storage__ = {
    線程/協程id:{}
    線程/協程id:{}
    線程/協程id:{}
}
class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
Local 源碼

而後咱們在回頭看下 LocalStark 是幹嗎的, 原來這貨是幫助維護這個數據結構的,

 會把 Local 的數據進一步維護成這樣

__storage__ = {
    線程/協程id:{stack:[ctx]}  # 維護成一個棧 
    線程/協程id:{stack:[ctx]}  
    線程/協程id:{stack:[ctx]}  
    線程/協程id:{stack:[ctx]}  
}    # 都是經過 Localstack 來操做 local 裏面的數據結構

 並且 LocalStark 裏面還有一系列的操做方法,因而可知 LocalStark 應該就是專門操做 Local 對象數據的

 

ok,走到頭了回去接着往下走

有封裝了一個 app_ctx ,其實剛剛看 top 的時候就已經發現了 下面有個 app_ctx_stack  了

可見其實封裝了兩個 LocalStark 對象,分別是 請求相關的 以及 app相關的

而後繼續往下走,這裏分裝了 app_ctx 是 AppContext 的 對象,

裏面封裝了,app 和 g 

看到了這裏

這裏就是對 session 的操做了。以前的 session 一直是 空,這裏進行真正的賦值。

可見 session 的賦值是經過配置文件的 配置名字 取到 session ,而後解密反序列化生成字典從新賦值 ctx.session 的具體操做實現

至此,push() 的代碼看完了。 

第二步總結

分裝了 兩個 ctx :

  - 請求上下文(ctx=RequestContext()):request/session
  - App上下文(app_ctx=AppContext()): app/g

  這兩 ctx 都分別保存了兩個 LocalStark 以及 兩個 Local  

以及 賦值了 ctx.session 

第三步 full_dispatch_request

這裏就不詳細看了。

大概作了兩件事,執行了處理視圖函數,以及一系列善後工做,

 第四步 彈出請求對象

視圖函數執行完畢後,一次請求結束,把請求的 ctx 對象彈出,

上下文管理

在看過了 總體的 Flask 的一次請求的流程以後,

咱們再來分析上下文管理 ,直接看到這個比較重點的地方把

兩個  LocalStack 對象,以及重要的 request,session 的產生的地方。

這裏使用了 LocalProxy用於代理Local對象和LocalStack對象,以及偏函數

流程

程序啓動

兩個Local:
  local1 = {}
                
  local2 = {}
        
兩個LocalStack:
  _request_ctx_stack 
  _app_ctx_stack

 請求到來

對數據進行封裝:
    ctx = RequestContext(request,session)
    app_ctx = AppContext(app,g)

保存數據 將包含了(app,g)數據的app_ctx對象,利用 _app_ctx_stack(LocalStack())將app_ctx添加到Local中 storage
= { 1231:{stack:[app_ctx(app,g),]} }
  將包含了request,session數據的ctx對象,利用_request_ctx_stack(LocalStack()),將ctx添加到Local中 storage
= { 1231:{stack:[ctx(request,session),]} }

視圖函數處理

@app.route('/index')
def index():
    # 去請求上下文中獲取值 _request_ctx_stack
    request.method 
    session['xxx'] 
    
    # 去app上下文中獲取值:_app_ctx_stack 
    print(current_app)
    print(g)
    
    return "Index"

 

方式一:直接找LocalStack獲取
        from flask.globals import _request_ctx_stack
        print(_request_ctx_stack.top.request.method)
        
方式二:經過代理LocalProx獲取
        from flask import Flask,request
        print(request.method)

請求結束

_app_ctx_stack.pop()
_request_ctx_stack.pop()

 上下文管理總流程圖

 

面試相關問題

問題一:flask和django的區別:
  對於django來講,內部組件特別多,自身功能強大,有點大而全,而flask,內置組件不多,可是它的第三方組件不少,擴展性強,有點短小精悍,而它們之間也有類似之處,
  由於它們兩個框架都沒有寫sockte,都是基於wsgi協議作的,在此以外,flask框架中的上下文管理較爲耀眼。

  
  相同點:它們兩個框架都沒有寫sockte,都是基於wsgi協議作的
  請求相關數據傳遞的方式不一樣:django:經過傳遞request參數取值
                flask:見問題二
           組件不一樣:django組件多
                flask組件少,第三方組件豐富

問題1.1: flask上下文管理:
  簡單來講,falsk上下文管理能夠分爲三個階段:
        1、請求進來時,將請求相關的數據放入上下問管理中
        2、在視圖函數中,要去上下文管理中取值
        3、請求響應,要將上下文管理中的數據清除
  
  詳細點來講:
        1、請求剛進來,將request,session封裝在RequestContext類中,app,g封裝在AppContext類中,並經過LocalStack將requestcontext和appcontext放入Local類中
        二、視圖函數中,經過localproxy--->偏函數--->localstack--->local取值
        3、請求相應時,先執行save.session()再各自執行pop(),將local中的數據清除
        

問題1.2  flask第三方組件
  flask:
      -flask-session    默認放入cookie,能夠放入redis
      -flask-redis
      -flask-migrate
      -flask-script
      -blinker  信號
   公共: DBUtils      數據庫鏈接池
      wtforms       表單驗證+生成HTML標籤
      sqlalchemy
  自定義:Auth   參考falsk-login

問題二:Flask中的session是何時建立,何時銷燬的?
  當請求進來時,會將requset和session封裝爲一個RequestContext對象,經過LocalStack將RequestContext放入到Local對象中,由於
請求第一次來session是空值,因此執行open_session,給session(uuid4())賦值,再經過視圖函數處理,請求響應時執行save.session,將簽名session寫入cookie中,再講Local中的數值pop掉。

問題三:flask中一共有幾個LocalStack和Local對象
  兩個LocalStack,兩個Local
  request、session共同用一個LocalStack和Local
  g、app共同用一個Localstack和Local

問題四: 爲何把請求放到RequestContext中:
   由於request和session都是在視圖中操做頻繁的數據,也是用戶請求須要用的數據,將request和session封裝在RequestContext中top,pop一次就能夠完成,而單獨不封裝在一塊兒就會屢次操做,

    ctx = RequestContext(request,session)

問題五:local做用
    -保存    請求上下文對象和app上下文對象

     -localstack的源碼與threading.local(線程處理)做用類似,不一樣之處是Local是經過greenlet(協程)獲取惟一標識,粒度更細
      
 問題六:Localstack做用
    2、將local對象中的數據維護成一個棧【ctx,ctx】(先進後出)
         {
            「協程或線程的惟一標識」: { stack:[ctx,ctx,ctx,] }
         }
    

爲何維護成一個棧?
    當是web應用時:不論是單線程仍是多線程,棧中只有一個數據
   - 服務端單線程:
        {
            111:{stack: [ctx, ]}
        }
   - 服務端多線程:
        {
            111:{stack: [ctx, ]}
            112:{stack: [ctx, ]}
        }
離線腳本:能夠在棧中放入多個數據
with app01.app_context():
                      print(current_app)
                      with app02.app_context():
                            print(current_app)
                      print(current_app)


 
 問題七:什麼是g?
    g 至關於一次請求的全局變量,當請求進來時將g和current_app封裝爲一個APPContext類,在經過LocalStack將Appcontext放入Local中,取值時經過偏函數,LocalStack、loca l中取值,響應時將local中的g數據刪除:
 問題八:怎麼獲取Session/g/current_app/request
    經過 、偏函數(lookup_req_object)、Localstack、Local取值
  問題九: 技術點:
          - 反射 (LocalProxy())
          - 面向對象,封裝:RequestContext
  - 線程(threading.local)
          - 筆試:本身寫一個類+列表 實現棧。(LocalStack)
問題十:python基本哪些內容比較重要:
1、反射
  -CBV
  -django配置文件
  -wtforms中的Form()示例化中 將"_fields中的數據封裝到From類中"
2、裝飾器 (迭代器,生成器)
  -flask:路由、裝飾器

  -認證
  -csrf
3、面向對象
  -繼承、封裝、多態(簡單描述)
 -雙下劃線:
    __mro__ wtform中 FormMeta中繼承類的優先級
     __dict__    
    __new__ ,實例化可是沒有給當前對象
                                        wtforms,字段實例化時返回:不是StringField,而是UnboundField
       rest frawork many=Turn  中的序列化
     __call__
                                       flask 請求的入口app.run()
                                        字段生成標籤時:字段.__str__ => 字段.__call__ => 插件.__call__
    
       __iter__ 循環對象是,自定義__iter__


        wtforms中BaseForm中循環全部字段時定義了__iter__
     metaclass
                                    - 做用:用於指定當前類使用哪一個類來建立
                                    - 場景:在類建立以前定製操做
                                            示例:wtforms中,對字段進行排序。
相關文章
相關標籤/搜索