前面第一篇主要記錄了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的縮寫
- class Flask(_PackageBoundObject):
-
-
- def wsgi_app(self, environ, start_response):
- ctx = self.request_context(environ)
- ctx.push()
再來看下request_context是一個什麼樣的方法,看看源碼
看他的返回值,他返回的實際上是RequestContext類生成的一個實例對象,看字面意思就知道是一個請求上下文的實例對象了.
這裏能夠注意看下他的函數說明,他舉了一個例子,很是簡單,ctx先push,最後再pop,和用with的方法做用是一毛同樣的
這其實就是一個請求到響應最簡單的骨架,側面反映了request的生命週期
- class Flask(_PackageBoundObject):
-
-
- def request_context(self, environ):
-
- return RequestContext(self, environ)
繼續往下層看,RequestContext是從ctx.py模塊中引入的,因此去找RequestContext的定義
- 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
-
-
- def push(self):
- top = _request_ctx_stack.top
- if top is not None and top.preserved:
- top.pop(top._preserved_exc)
-
- app_ctx = _app_ctx_stack.top
- if app_ctx is None or app_ctx.app != self.app:
- app_ctx = self.app.app_context()
- 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.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,打開後發現,原來全部的上下文環境的定義,都在這裏,怪不得名字取成全局變量
- def _lookup_req_object(name):
- top = _request_ctx_stack.top
- if top is None:
- raise RuntimeError(_request_ctx_err_msg)
- return getattr(top, name)
-
-
- def _lookup_app_object(name):
- top = _app_ctx_stack.top
- if top is None:
- raise RuntimeError(_app_ctx_err_msg)
- return getattr(top, name)
-
-
- def _find_app():
- top = _app_ctx_stack.top
- if top is None:
- raise RuntimeError(_app_ctx_err_msg)
- return top.app
-
-
- _request_ctx_stack = LocalStack()
- _app_ctx_stack = LocalStack()
- current_app = LocalProxy(_find_app)
- request = LocalProxy(partial(_lookup_req_object, 'request'))
- session = LocalProxy(partial(_lookup_req_object, 'session'))
- g = LocalProxy(partial(_lookup_app_object, 'g'))
上下文的數據結構分析
看到 _request_ctx_stack是LocalStack的實例對象,那就去找LocalStack的源碼了,他來自於werkzeug工具包裏面的local模塊
- class LocalStack(object):
-
- def __init__(self):
- self._local = Local()
-
-
- def push(self, obj):
-
- rv = getattr(self._local, 'stack', None)
- if rv is None:
- self._local.stack = rv = []
- rv.append(obj)
- return rv
-
-
- @property
- def top(self):
-
- try:
- return self._local.stack[-1]
- except (AttributeError, IndexError):
- return None
其中最主要的三個方法是,__init__初始化方法, push壓棧方法,以及top元素的訪問方法
__init__初始化方法其實很簡單,他把LocalStack的實例(也就是_request_ctx_stack)的_local屬性,設置爲了Local類的實例
因此這裏須要先看一下Local類的定義,他和LocalStack在同一個模塊內
- class Local(object):
- __slots__ = ('__storage__', '__ident_func__')
-
- def __init__(self):
- object.__setattr__(self, '__storage__', {})
- object.__setattr__(self, '__ident_func__', get_ident)
-
- def __call__(self, proxy):
-
- return LocalProxy(self, proxy)
-
- 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}
Local類的實例對象,其實就是包含了2個屬性
一個叫 __storage__ 的字典
另外一個叫 __ident_func__ 的方法,他這個方法實際上是get_ident,這個方法很少說,他是從_thread內置模塊裏面導入的,他的做用是返回線程號
這部分有點繞,由於在Local和LocalStack兩個類裏面來回穿梭
Local類的定義看完之後,回過去看LocalStack的push方法
- def push(self, obj):
-
- rv = getattr(self._local, 'stack', None)
- if rv is None:
- self._local.stack = rv = []
- rv.append(obj)
- return rv
他會先去取 LocalStack實例的_local屬性,也就是Local()實例的stack屬性, 若是沒有這個屬性,則返回None
若是是None的話,則開始創建上下文棧結構,返回值rv表明上下文的總體結構
_local的stack屬性就是一個棧結構
這裏的obj,實際上是對應最一開頭的RequestContext裏面的push方法裏的self,也就是,他在push的時候,傳入的對象是上下文RequestContext的實例對象
這裏要再看一下Local類的__setattr__方法了,看看他如何賦值
- def __setattr__(self, name, value):
- ident = self.__ident_func__()
- storage = self.__storage__
- try:
- storage[ident][name] = value
- except KeyError:
- 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造成的一個代理
- 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實例建立的時候,就被創建起來了
- 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
這個是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之類
- class Request(BaseRequest, AcceptMixin, ETagRequestMixin,
- UserAgentMixin, AuthorizationMixin,
- CommonRequestDescriptorsMixin):
-
-
因此說,經過RequestContext上下文環境被壓入棧的過程,flask將app和request進行了掛鉤.
LocalProxy究竟是一個什麼東西
LocalProxy的源代碼太長了,就不貼了,關鍵看下LocalProxy和Local及LocalProxy之間的關係
Local和LocalStack的__call__方法,都會將實例,轉化成LocalProxy對象
- class LocalStack(object):
-
-
- def __call__(self):
- def _lookup():
- rv = self.top
- if rv is None:
- raise RuntimeError('object unbound')
- return rv
- return LocalProxy(_lookup)
- class Local(object):
-
-
- def __call__(self, proxy):
-
- return LocalProxy(self, proxy)
而LocalProxy最關鍵的就是一個_get_current_object方法,一個__getattr__的重寫
- @implements_bool
- class LocalProxy(object):
-
-
- __slots__ = ('__local', '__dict__', '__name__')
-
- def __init__(self, local, name=None):
- object.__setattr__(self, '_LocalProxy__local', local)
- object.__setattr__(self, '__name__', name)
-
- def _get_current_object(self):
-
-
- def __getattr__(self, name):
- if name == '__members__':
- return dir(self._get_current_object())
- return getattr(self._get_current_object(), name)
__getattr__方法和 _get_current_object方法聯合一塊兒,返回了真實對象的name屬性,name就是你想要獲取的信息.
這樣,你就能夠經過request.name 來進行request內部信息的訪問了。