Flask上下文源碼分析(二)

前面第一篇主要記錄了Flask框架,從http請求發起,到返回響應,發生在server和app直接的過程。python

裏面有說到,Flask框架有設計了兩種上下文,即應用上下文和請求上下文flask

官方文檔裏是說先理解應用上下文比較好,不過我仍是以爲反過來,從請求上下文開始記錄比較合適,因此這篇先記錄請求上下文。session

 

 

什麼是請求上下文

通俗點說,其實上下文就像一個容器,包含了不少你須要的信息數據結構

request和session都屬於請求上下文app

request 針對的是http請求做爲對象框架

session針對的是更可能是用戶信息做爲對象ide

 

上下文的結構

說到上下文這個概念的數據結構,這裏須要先知道,他是運用了一個Stack的棧結構,也就說,有棧所擁有的特性,push,top,pop等函數

 

請求上下文  -----  RequestContext

當一個請求進來的時候,請求上下文環境是如何運做的呢?仍是須要來看一下源碼工具

上一篇有講到,當一個請求從server傳遞過來的時候,他會調用Flask的__call__方法,因此這裏仍是回到wsgi_app那部分去講this

 

下面是當wsgi_app被調用的時候,最一開始的動做,這裏的ctx是context的縮寫

[python]  view plain  copy
 
  1. class Flask(_PackageBoundObject):  
  2.   
  3. # 省略一部分代碼  
  4.   
  5.     def wsgi_app(self, environ, start_response):  
  6.         ctx = self.request_context(environ)     #上下文變量ctx被賦值爲request_context(environ)的值  
  7.         ctx.push()                              #  

 

 

 

再來看下request_context是一個什麼樣的方法,看看源碼

看他的返回值,他返回的實際上是RequestContext類生成的一個實例對象,看字面意思就知道是一個請求上下文的實例對象了.

這裏能夠注意看下他的函數說明,他舉了一個例子,很是簡單,ctx先push,最後再pop,和用with的方法做用是一毛同樣的

這其實就是一個請求到響應最簡單的骨架,側面反映了request的生命週期

[python]  view plain  copy
 
  1. class Flask(_PackageBoundObject):  
  2.   
  3. #省略部分代碼  
  4.   
  5.     def request_context(self, environ):  
  6.         """ctx = app.request_context(environ) 
  7.         ctx.push() 
  8.         try: 
  9.             do_something_with(request) 
  10.         finally: 
  11.             ctx.pop()"""  
  12.         return RequestContext(self, environ)  

 

 

繼續往下層看,RequestContext是從ctx.py模塊中引入的,因此去找RequestContext的定義

[python]  view plain  copy
 
  1. class RequestContext(object):  
  2.     """The request context contains all request relevant information.  It is 
  3.     created at the beginning of the request and pushed to the 
  4.     `_request_ctx_stack` and removed at the end of it.  It will create the 
  5.     URL adapter and request object for the WSGI environment provided. 
  6.  
  7.     Do not attempt to use this class directly, instead use 
  8.     :meth:`~flask.Flask.test_request_context` and 
  9.     :meth:`~flask.Flask.request_context` to create this object."""  
  10.   
  11. #省略部分說明  
  12.   
  13.     def __init__(self, app, environ, request=None):  
  14.         self.app = app  
  15.         if request is None:  
  16.             request = app.request_class(environ)  
  17.         self.request = request  
  18.         self.url_adapter = app.create_url_adapter(self.request)  
  19.         self.flashes = None  
  20.         self.session = None  
  21.   
  22. #省略部分代碼  
  23.   
  24.     def push(self):  
  25.         top = _request_ctx_stack.top  
  26.         if top is not None and top.preserved:  
  27.             top.pop(top._preserved_exc)  
  28.   
  29.         app_ctx = _app_ctx_stack.top  
  30.         if app_ctx is None or app_ctx.app != self.app:  
  31.             app_ctx = self.app.app_context()  
  32.             app_ctx.push()  
  33.             self._implicit_app_ctx_stack.append(app_ctx)  
  34.         else:  
  35.             self._implicit_app_ctx_stack.append(None)  
  36.   
  37.         if hasattr(sys, 'exc_clear'):  
  38.             sys.exc_clear()  
  39.   
  40.         _request_ctx_stack.push(self)  

 

注意一下__init__方法,他的第一個參數是app實例對象,因此在前面額app.py文件內,他的生成方法第一個參數是self,另外,還要傳入environ參數

這樣,回到wsgi_app的函數內部,咱們其實已經有了ctx這個變量的值了

因此接下去的一步就是很是重要的ctx.push()了

首先會判斷上下文棧的頂端是否有元素,若是是沒元素的,就返回None

若是有元素,會彈出該元素

接着看最後一行,會進行_request_ctx_stack的push動做,參數是self,這裏的self實際上就是上下文實例  ctx,也就是說,把上下文的內容進行壓棧,放到棧頂了。

 

看到這裏,又引入了一個新的對象 _request_ctx_stack,這實際上是一個很是重要的概念,他就是上下文環境的數據結構,也就是棧結構

 

繼續找這個對象來自哪裏,發現他來自於同級目錄的globals,打開後發現,原來全部的上下文環境的定義,都在這裏,怪不得名字取成全局變量

 

[python]  view plain  copy
 
  1. def _lookup_req_object(name):  
  2.     top = _request_ctx_stack.top  
  3.     if top is None:  
  4.         raise RuntimeError(_request_ctx_err_msg)  
  5.     return getattr(top, name)  
  6.   
  7.   
  8. def _lookup_app_object(name):  
  9.     top = _app_ctx_stack.top  
  10.     if top is None:  
  11.         raise RuntimeError(_app_ctx_err_msg)  
  12.     return getattr(top, name)  
  13.   
  14.   
  15. def _find_app():  
  16.     top = _app_ctx_stack.top  
  17.     if top is None:  
  18.         raise RuntimeError(_app_ctx_err_msg)  
  19.     return top.app  
  20.   
  21.   
  22. # context locals  
  23. _request_ctx_stack = LocalStack()                    #請求上下文的數據結構  
  24. _app_ctx_stack = LocalStack()                        #引用上下文的數據結構  
  25. current_app = LocalProxy(_find_app)                  #從這個開始的4個,都是全局變量,他們其實經過代理上下文來實現的  
  26. request = LocalProxy(partial(_lookup_req_object, 'request'))  
  27. session = LocalProxy(partial(_lookup_req_object, 'session'))  
  28. g = LocalProxy(partial(_lookup_app_object, 'g'))  

 

 

上下文的數據結構分析

看到   _request_ctx_stack是LocalStack的實例對象,那就去找LocalStack的源碼了,他來自於werkzeug工具包裏面的local模塊

 

[python]  view plain  copy
 
  1. class LocalStack(object):  
  2.   
  3.     def __init__(self):  
  4.         self._local = Local()  
  5.   
  6. #中間省略部分代碼  
  7.   
  8.     def push(self, obj):  
  9.         """Pushes a new item to the stack"""  
  10.         rv = getattr(self._local, 'stack', None)  
  11.         if rv is None:  
  12.             self._local.stack = rv = []  
  13.         rv.append(obj)  
  14.         return rv  
  15.   
  16. #中間省略部分代碼  
  17.  
  18.     @property  
  19.     def top(self):  
  20.         """The topmost item on the stack.  If the stack is empty, 
  21.         `None` is returned. 
  22.         """  
  23.         try:  
  24.             return self._local.stack[-1]  
  25.         except (AttributeError, IndexError):  
  26.             return None  

其中最主要的三個方法是,__init__初始化方法, push壓棧方法,以及top元素的訪問方法
__init__初始化方法其實很簡單,他把LocalStack的實例(也就是_request_ctx_stack)的_local屬性,設置爲了Local類的實例

 

 

 

 

因此這裏須要先看一下Local類的定義,他和LocalStack在同一個模塊內

 

[python]  view plain  copy
 
  1. class Local(object):  
  2.     __slots__ = ('__storage__', '__ident_func__')  
  3.   
  4.     def __init__(self):  
  5.         object.__setattr__(self, '__storage__', {})  
  6.         object.__setattr__(self, '__ident_func__', get_ident)  
  7.   
  8.     def __call__(self, proxy):  
  9.         """Create a proxy for a name."""  
  10.         return LocalProxy(self, proxy)  
  11.   
  12.     def __getattr__(self, name):  
  13.         try:  
  14.             return self.__storage__[self.__ident_func__()][name]  
  15.         except KeyError:  
  16.             raise AttributeError(name)  
  17.   
  18.     def __setattr__(self, name, value):  
  19.         ident = self.__ident_func__()  
  20.         storage = self.__storage__  
  21.         try:  
  22.             storage[ident][name] = value  
  23.         except KeyError:  
  24.             storage[ident] = {name: value}  


Local類的實例對象,其實就是包含了2個屬性

 

 

 

一個叫  __storage__  的字典

另外一個叫 __ident_func__ 的方法,他這個方法實際上是get_ident,這個方法很少說,他是從_thread內置模塊裏面導入的,他的做用是返回線程號

 

這部分有點繞,由於在Local和LocalStack兩個類裏面來回穿梭

Local類的定義看完之後,回過去看LocalStack的push方法

 

[python]  view plain  copy
 
  1. def push(self, obj):  
  2.     """Pushes a new item to the stack"""  
  3.     rv = getattr(self._local, 'stack', None)  
  4.     if rv is None:  
  5.         self._local.stack = rv = []  
  6.     rv.append(obj)  
  7.     return rv  

他會先去取 LocalStack實例的_local屬性,也就是Local()實例的stack屬性, 若是沒有這個屬性,則返回None

 

 

 

若是是None的話,則開始創建上下文棧結構,返回值rv表明上下文的總體結構

_local的stack屬性就是一個棧結構
這裏的obj,實際上是對應最一開頭的RequestContext裏面的push方法裏的self,也就是,他在push的時候,傳入的對象是上下文RequestContext的實例對象

這裏要再看一下Local類的__setattr__方法了,看看他如何賦值

[python]  view plain  copy
 
  1. def __setattr__(self, name, value):  
  2.     ident = self.__ident_func__()  
  3.     storage = self.__storage__  
  4.     try:  
  5.         storage[ident][name] = value  
  6.     except KeyError:  
  7.         storage[ident] = {name: value}  

他實際上是一個字典嵌套的形式,由於__storage__自己就是一個字典,而name和value又是一組鍵值

注意,value自己也是一個容器,是list

因此,他的內部形式其實是 __storage__ ={{ident1:{name1:value1}},{ident2:{name2:value2}},{ident3:{name3:value3}}}

他的取值方式__getattr__  就是__storage__[self.__ident_func__()][name]

這樣每一個線程對應的上下文棧都是本身自己,不會搞混。

 

至此,當一個請求上下文環境被創建完以後,到儲存到棧結構頂端的過程,就完成了。

這個時候,棧頂元素裏面已經包含了大量的信息了,包括像這篇文章裏面最重要的概念的request也包含在裏面了

 

全局變量request

來看一下request的定義,他實際上是棧頂元素的name屬性,通過LocalProxy造成的一個代理

 

[python]  view plain  copy
 
  1. request = LocalProxy(partial(_lookup_req_object, 'request'))  

 

以上代碼能夠當作是  request = LocalProxy(_request_ctx_stack.top.request) = LocalProxy (_request_ctx_stack._local[stack][-1].request)

也就是棧頂元素內,name叫作request對象的值,而這個值,包含了不少的內容,包括像 HTTP請求頭的信息,都包括在內,能夠提供給全局使用

 

可是,這個request對象,早在RequestContext實例建立的時候,就被創建起來了

 

[python]  view plain  copy
 
  1. def __init__(self, app, environ, request=None):  
  2.     self.app = app  
  3.     if request is None:  
  4.         request = app.request_class(environ)  
  5.     self.request = request  
  6.     self.url_adapter = app.create_url_adapter(self.request)  
  7.     self.flashes = None  
  8.     self.session = None  

這個是RequestContext類的定義,他的實例有request=app.request_class屬性

 

實例被壓入上下文棧頂以後,只是經過LocalProxy造成了新的代理後的request,可是內容實際上是前面建立的。

因此說,他纔可以使用request這個屬性來進行請求對象的訪問

 

 

 

request來自於Request類

上面的request對象,是經過RequestContext的定義中

request = app.request_class(environ)創建起來的,而request_class = Request類,而Request類則是取自於werkzeuk的 wrappers模塊

這個有空再研究了,主要仍是和HTTP請求信息有關係的,好比header parse,ETAG,user Agent之類

 

[python]  view plain  copy
 
  1. class Request(BaseRequest, AcceptMixin, ETagRequestMixin,  
  2.               UserAgentMixin, AuthorizationMixin,  
  3.               CommonRequestDescriptorsMixin):  
  4.   
  5.     """Full featured request object implementing the following mixins: 
  6.  
  7.     - :class:`AcceptMixin` for accept header parsing 
  8.     - :class:`ETagRequestMixin` for etag and cache control handling 
  9.     - :class:`UserAgentMixin` for user agent introspection 
  10.     - :class:`AuthorizationMixin` for http auth handling 
  11.     - :class:`CommonRequestDescriptorsMixin` for common headers 
  12.     """  

 

因此說,經過RequestContext上下文環境被壓入棧的過程,flask將app和request進行了掛鉤.

 

LocalProxy究竟是一個什麼東西

LocalProxy的源代碼太長了,就不貼了,關鍵看下LocalProxy和Local及LocalProxy之間的關係

Local和LocalStack的__call__方法,都會將實例,轉化成LocalProxy對象

 

[python]  view plain  copy
 
  1. class LocalStack(object):  
  2.   
  3. #省略部分代碼  
  4.   
  5.     def __call__(self):  
  6.         def _lookup():  
  7.             rv = self.top  
  8.             if rv is None:  
  9.                 raise RuntimeError('object unbound')  
  10.             return rv  
  11.         return LocalProxy(_lookup)  



[python]  view plain  copy
 
  1. class Local(object):  
  2.   
  3. #省略部分代碼  
  4.   
  5.     def __call__(self, proxy):  
  6.         """Create a proxy for a name."""  
  7.         return LocalProxy(self, proxy)  

 

 

而LocalProxy最關鍵的就是一個_get_current_object方法,一個__getattr__的重寫

[python]  view plain  copy
 
  1. @implements_bool  
  2. class LocalProxy(object):  
  3.   
  4. #省略部分代碼  
  5.   
  6.     __slots__ = ('__local', '__dict__', '__name__')  
  7.   
  8.     def __init__(self, local, name=None):  
  9.         object.__setattr__(self, '_LocalProxy__local', local)  
  10.         object.__setattr__(self, '__name__', name)  
  11.   
  12.     def _get_current_object(self):  
  13.         """Return the current object.  This is useful if you want the real 
  14.         object behind the proxy at a time for performance reasons or because 
  15.         you want to pass the object into a different context. 
  16.      if not hasattr(self.__local, '__release_local__'): 
  17.         return self.__local() 
  18.      try: 
  19.         return getattr(self.__local, self.__name__) 
  20.      except AttributeError: 
  21.         raise RuntimeError('no object bound to %s' % self.__name__)       """  
  22.   
  23.     def __getattr__(self, name):  
  24.         if name == '__members__':  
  25.             return dir(self._get_current_object())  
  26.         return getattr(self._get_current_object(), name)  

 

__getattr__方法和 _get_current_object方法聯合一塊兒,返回了真實對象的name屬性,name就是你想要獲取的信息.

這樣,你就能夠經過request.name 來進行request內部信息的訪問了。

相關文章
相關標籤/搜索