flask的上下文管理分应用上下文和请求上下文:
官方文档里是说先理解应用上下文比较好,不过我还是觉得反过来,从请求上下文开始记录比较合适,所以这篇先记录请求上下文。
那么问题来了,什么才是请求上下文:
通俗点说,其实上下文就像一个容器,包含了很多你需要的信息
request和session都属于请求上下文
request 针对的是http请求作为对象
session针对的是更多是用户信息作为对象
上下文的结构
说到上下文这个概念的数据结构,这里需要先知道,他是运用了一个Stack的栈结构,也就说,有栈所拥有的特性,push,top,pop等
请求上下文 ----- RequestContext
当一个请求进来的时候,请求上下文环境是如何运作的呢?还是需要来看一下源码
下面是当wsgi_app被调用的时候,最一开始的动作,这里的ctx是context的缩写
class Flask(_PackageBoundObject): # 省略一部分代码 def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) #上下文变量ctx被赋值为request_context(environ)的值 ctx.push() #
再来看下request_context是一个什么样的方法,看看源码
看他的返回值,他返回的其实是RequestContext类生成的一个实例对象,看字面意思就知道是一个请求上下文的实例对象了.
这里可以注意看下他的函数说明,他举了一个例子,非常简单,ctx先push,最后再pop,和用with的方法作用是一毛一样的
这其实就是一个请求到响应最简单的骨架,侧面反映了request的生命周期
class Flask(_PackageBoundObject): #省略部分代码 def request_context(self, environ): """ctx = app.request_context(environ) ctx.push() try: do_something_with(request) finally: ctx.pop()""" return RequestContext(self, environ)
继续往下层看,RequestContext是从ctx.py模块中引入的,所以去找RequestContext的定义
class RequestContext(object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it. It will create the URL adapter and request object for the WSGI environment provided. Do not attempt to use this class directly, instead use :meth:`~flask.Flask.test_request_context` and :meth:`~flask.Flask.request_context` to create this 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 # context locals _request_ctx_stack = LocalStack() #请求上下文的数据结构 _app_ctx_stack = LocalStack() #引用上下文的数据结构 current_app = LocalProxy(_find_app) #从这个开始的4个,都是全局变量,他们其实通过代理上下文来实现的 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): """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 #中间省略部分代码 @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
其中最主要的三个方法是,__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): """Create a proxy for a name.""" 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): """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
他会先去取 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): """Full featured request object implementing the following mixins: - :class:`AcceptMixin` for accept header parsing - :class:`ETagRequestMixin` for etag and cache control handling - :class:`UserAgentMixin` for user agent introspection - :class:`AuthorizationMixin` for http auth handling - :class:`CommonRequestDescriptorsMixin` for common headers """
所以说,通过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): """Create a proxy for a name.""" 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): """Return the current object. This is useful if you want the real object behind the proxy at a time for performance reasons or because you want to pass the object into a different context. if not hasattr(self.__local, ‘__release_local__‘): return self.__local() try: return getattr(self.__local, self.__name__) except AttributeError: raise RuntimeError(‘no object bound to %s‘ % self.__name__) """ 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内部信息的访问了。