前面第一篇主要记录了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. jupyter修改默认目录

    有趣的事,Python永远不会缺席! 如需转发,请注明出处:小婷儿的python https://www.cnblogs.com/xxtalhr/p/10841241.html 一.修改 win10 ...

  2. Python requests.post嵌套多层json参数调用接口

    #coding:utf-8 import requests,json #第一行注解的#coding:utf-8表示可以支持中文,不然代码里面有中文会报错 url = "http://xxx& ...

  3. IO五种模型和select与epoll工作原理(引入nginx)

    用户速度体验的1-3-10原则 性能影响 有很多研究都表明,性能对用户的行为有很大的影响: 79%的用户表示不太可能再次打开一个缓慢的网站 47%的用户期望网页能在2秒钟以内加载 40%的用户 ...

  4. Java线程(1)

    多线程快速入门 线程与进程区别 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行.也可以把它理解为代码运行的上下文.所以 ...

  5. 虚拟dom应用

    vdom如何应用,核心api是什么 1.介绍snabbdom(开源社区用的多,vue2用的是他) 首先回顾下之前的vdom格式 真实的dom <body> <ul id=" ...

  6. JDBC终章- 使用 DBUtils实现增删查改- C3P0Utils数据源/QueryRunner runner连接数据源并执行sql

    JDBC终章- 使用 DBUtils实现增删查改 1.数据库结构 Create Table CREATE TABLE `user` ( `id` ) NOT NULL AUTO_INCREMENT, ...

  7. 使用宏定义来判断是a和b 的大小

    #include <stdio.h> #include <math.h> #define MAX(a, b) (a) > (b) ? printf("a > ...

  8. nginx url默认去掉index.php

  9. MySQL的索引优化,查询优化

    MySQL逻辑架构 如果能在头脑中构建一幅MySQL各组件之间如何协同工作的架构图,有助于深入理解MySQL服务器.下图展示了MySQL的逻辑架构图. MySQL逻辑架构,来自:高性能MySQL My ...

  10. 企业级本地yum源配置方案详解

    因目前企业生产网络禁止联网,对于使用Linux的我们来说,非常不方便,想要使用yum源都很困难,挂dvd又不能完全满足要求,所以自建一个企业级的yum源,定时从公网同步到本地,然后生产网络直接配置在本 ...