前面第一篇主要记录了Flask框架,从http请求发起,到返回响应,发生在server和app直接的过程。

里面有说到,Flask框架有设计了两种上下文,即应用上下文和请求上下文

官方文档里是说先理解应用上下文比较好,不过我还是觉得反过来,从请求上下文开始记录比较合适,所以这篇先记录请求上下文。

什么是请求上下文

通俗点说,其实上下文就像一个容器,包含了很多你需要的信息

request和session都属于请求上下文

request 针对的是http请求作为对象

session针对的是更多是用户信息作为对象

上下文的结构

说到上下文这个概念的数据结构,这里需要先知道,他是运用了一个Stack的栈结构,也就说,有栈所拥有的特性,push,top,pop等

请求上下文  -----  RequestContext

当一个请求进来的时候,请求上下文环境是如何运作的呢?还是需要来看一下源码

上一篇有讲到,当一个请求从server传递过来的时候,他会调用Flask的__call__方法,所以这里还是回到wsgi_app那部分去讲

下面是当wsgi_app被调用的时候,最一开始的动作,这里的ctx是context的缩写

  1. class Flask(_PackageBoundObject):
  2. # 省略一部分代码
  3. def wsgi_app(self, environ, start_response):
  4. ctx = self.request_context(environ)     #上下文变量ctx被赋值为request_context(environ)的值
  5. ctx.push()                              #

再来看下request_context是一个什么样的方法,看看源码

看他的返回值,他返回的其实是RequestContext类生成的一个实例对象,看字面意思就知道是一个请求上下文的实例对象了.

这里可以注意看下他的函数说明,他举了一个例子,非常简单,ctx先push,最后再pop,和用with的方法作用是一毛一样的

这其实就是一个请求到响应最简单的骨架,侧面反映了request的生命周期

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

继续往下层看,RequestContext是从ctx.py模块中引入的,所以去找RequestContext的定义

  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. Do not attempt to use this class directly, instead use
  7. :meth:`~flask.Flask.test_request_context` and
  8. :meth:`~flask.Flask.request_context` to create this object."""
  9. #省略部分说明
  10. def __init__(self, app, environ, request=None):
  11. self.app = app
  12. if request is None:
  13. request = app.request_class(environ)
  14. self.request = request
  15. self.url_adapter = app.create_url_adapter(self.request)
  16. self.flashes = None
  17. self.session = None
  18. #省略部分代码
  19. def push(self):
  20. top = _request_ctx_stack.top
  21. if top is not None and top.preserved:
  22. top.pop(top._preserved_exc)
  23. app_ctx = _app_ctx_stack.top
  24. if app_ctx is None or app_ctx.app != self.app:
  25. app_ctx = self.app.app_context()
  26. app_ctx.push()
  27. self._implicit_app_ctx_stack.append(app_ctx)
  28. else:
  29. self._implicit_app_ctx_stack.append(None)
  30. if hasattr(sys, 'exc_clear'):
  31. sys.exc_clear()
  32. _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,打开后发现,原来所有的上下文环境的定义,都在这里,怪不得名字取成全局变量

  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. def _lookup_app_object(name):
  7. top = _app_ctx_stack.top
  8. if top is None:
  9. raise RuntimeError(_app_ctx_err_msg)
  10. return getattr(top, name)
  11. def _find_app():
  12. top = _app_ctx_stack.top
  13. if top is None:
  14. raise RuntimeError(_app_ctx_err_msg)
  15. return top.app
  16. # context locals
  17. _request_ctx_stack = LocalStack()                    #请求上下文的数据结构
  18. _app_ctx_stack = LocalStack()                        #引用上下文的数据结构
  19. current_app = LocalProxy(_find_app)                  #从这个开始的4个,都是全局变量,他们其实通过代理上下文来实现的
  20. request = LocalProxy(partial(_lookup_req_object, 'request'))
  21. session = LocalProxy(partial(_lookup_req_object, 'session'))
  22. g = LocalProxy(partial(_lookup_app_object, 'g'))

上下文的数据结构分析

看到   _request_ctx_stack是LocalStack的实例对象,那就去找LocalStack的源码了,他来自于werkzeug工具包里面的local模块

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

其中最主要的三个方法是,__init__初始化方法, push压栈方法,以及top元素的访问方法
__init__初始化方法其实很简单,他把LocalStack的实例(也就是_request_ctx_stack)的_local属性,设置为了Local类的实例

所以这里需要先看一下Local类的定义,他和LocalStack在同一个模块内

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

Local类的实例对象,其实就是包含了2个属性

一个叫  __storage__  的字典

另一个叫 __ident_func__ 的方法,他这个方法其实是get_ident,这个方法不多说,他是从_thread内置模块里面导入的,他的作用是返回线程号

这部分有点绕,因为在Local和LocalStack两个类里面来回穿梭

Local类的定义看完以后,回过去看LocalStack的push方法

  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__方法了,看看他如何赋值

  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形成的一个代理

  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实例创建的时候,就被建立起来了

  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之类

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

所以说,通过RequestContext上下文环境被压入栈的过程,flask将app和request进行了挂钩.

LocalProxy到底是一个什么东西

LocalProxy的源代码太长了,就不贴了,关键看下LocalProxy和Local及LocalProxy之间的关系

Local和LocalStack的__call__方法,都会将实例,转化成LocalProxy对象

  1. class LocalStack(object):
  2. #省略部分代码
  3. def __call__(self):
  4. def _lookup():
  5. rv = self.top
  6. if rv is None:
  7. raise RuntimeError('object unbound')
  8. return rv
  9. return LocalProxy(_lookup)
  1. class Local(object):
  2. #省略部分代码
  3. def __call__(self, proxy):
  4. """Create a proxy for a name."""
  5. return LocalProxy(self, proxy)

而LocalProxy最关键的就是一个_get_current_object方法,一个__getattr__的重写

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

__getattr__方法和 _get_current_object方法联合一起,返回了真实对象的name属性,name就是你想要获取的信息.

这样,你就可以通过request.name 来进行request内部信息的访问了。

Flask上下文源码分析(二)的更多相关文章

  1. Flask上下文源码分析(一)

    flask中的上下文分两种,application context和request context,即应用上下文和请求上下文.   从名字上看,可能会有误解,认为应用上下文是一个应用的全局变量,所有请 ...

  2. Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号

    Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...

  3. Flask系列10-- Flask请求上下文源码分析

    总览 一.基础准备. 1. local类 对于一个类,实例化得到它的对象后,如果开启多个线程对它的属性进行操作,会发现数据时不安全的 import time from threading import ...

  4. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  5. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  6. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  7. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  8. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  9. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

随机推荐

  1. 使用Django时需要注意的八个要点

    1.在settings.py中使用os. path.dirname() 常用代码如下: # settings.py import os PROJECT_DIR = os.path.dirname(__ ...

  2. mysql建表常用命令

    MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品.MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RD ...

  3. TensorFlow实现自编码器及多层感知机

    1 自动编码机简介        传统机器学习任务在很大程度上依赖于好的特征工程,比如对数值型,日期时间型,种类型等特征的提取.特征工程往往是非常耗时耗力的,在图像,语音和视频中提取到有效的特征就更难 ...

  4. 移动端隐藏滚动条,css方法

    小白第一次发文记录自己遇到的问题. 关于隐藏移动端滚动条方法很多,这里只说本人用到的. 在PC端隐藏html右侧默认滚动条 html { /*隐藏滚动条,当IE下溢出,仍然可以滚动*/ -ms-ove ...

  5. 本地数据存储解决方案以及cookie的坑

    本地数据存储解决方案以及cookie的坑 问题: cookie过长导致页面打开失败 背景: 在公司的项目中有一个需求是打开多个工单即在同一个页面中打开了多个tab(iframe),但是需要在刷新时只刷 ...

  6. stm32和sd卡

    SD卡从容量上讲分两种:标准容量和大容量,最小的是标准容量,小于等于2G 其中的访问关系如下: SD卡分为两种模式:认证模式和传输模式,每一个模式包含着不同的状态,如下 以下主要讲其初始化过程: SD ...

  7. 重构drf后的环境变量配置

    目录 环境变量 配置media 封装logger 封装项目异常处理 二次封装Response模块 环境变量 dev.py # 环境变量操作:小luffyapiBASE_DIR与apps文件夹都要添加到 ...

  8. frp服务搭建

    安装命令 C wget --no-check-certificate https://raw.githubusercontent.com/clangcn/onekey-install-shell/ma ...

  9. IDEA实用教程(五)——配置IDEA的JVM内存值

    ---恢复内容开始--- 四. 配置IDEA的JVM内存值 IDEA默认配置的JVM内存值比较低,如果硬件配置较高,可以修改该设置. 该设置需要在工程界面进行. 该操作仅建议内存8G以上,64位操作系 ...

  10. 刷题小tips

    自从认真做题以后,结合自己的做题体验以及网上前人的总结经验贴,罗列下列的小建议: 1.为了避免每一次都要重复输入测试数据的麻烦,利用输入重定向文件 我是只把标准输入stdin重定位到in.txt,然后 ...