前面第一篇主要记录了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内部信息的访问了。