基本流程概述

- 与django相比是两种不同的实现方式。
- django/tornado是通过传参数形式实现
- 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了。
- 上下文管理:
- 说上下文管理前要先提一下threadinglocal,它为每一个线程开辟一块独立的空间,但是Flask不是用它做得,它自己实现类一个local类
- 其中创建了一个字典来保存数据,这个字典的key是用线程的唯一标识,如果有协程用greelet拿的一个唯一标识,可以是线程的也可以支持协程,后面是存取的数据{greenlet做唯一标识:存数据} 这样可以保证数据隔离
- 当请求进来的时候:
- 将请求相关所有数据(request,session)封装到了RequestContext中。
- 再将RequestContext对象通过LocalStack类添加到Local中(调用push方法)
- 使用时,调用request
- 调用此类方法 request.method、print(request)、request+xxx 会执行LocalProxy中对应的魔法方法
- 魔法方法中调用_get_current_object函数
- 通过LocalStack中的top方法,去Local中获取值,取的是列表的最后一个值。
- 请求终止时
- 还是通过LocalStack的pop方法 将Local中将值在列表中pop掉。
内置session流程

当请求刚进来的时候,会把请求相关的和session封装到RequestContext的对象中去,RequestCcontext对像再通过它里面对push方法把对象放到Flask特有的,类似与theadingLocal那么一个Local对象中去,
push中会调用session里面open_session方法,通过这个方法帮助我们获取用户原有的session信息,有就获取,没有就返回一个空的类似字典的数据结构,赋值给对象中的session 当使用的时候触发LocalProxy对像里对魔法方法,再调用get_current_obj,通过偏函数用Localstark中方法去Local获取到数据 使用完后,调用session对像的save_session方法,将数据加密写到用户的cookie中,这个操作在after_request之后

 request 与 session

flask中要想调用当前请求的request对象,需要使用from flask import reuqest导入。当我们导入request全局对象的时候,本质上是从Local中本次请求线程对应的RequestContext内封装的request_context_obj.request对象。
除了request对象,session、g 以及current_app都是这个原理

LocalStack类与Local类

Local类

  是flask模仿threading.Local实现的一个本地线程,内部的self.__storage__封装了一个字典,用来存放每一个请求对应线程的私有数据数据,保证了每一个请求之间的数据隔离。

  他的结构是这样

  •  其中RequestContext对象中封装了当前请求的request对象和session对象
self.__storage__ = {
greenlet获取的唯一标识:{"stack": [RequestContext]},
greenlet获取的唯一标识:{"stack": [RequestContext]},
greenlet获取的唯一标识:{"stack": [RequestContext]},
}
__storage__是一个字典,但是在Local中还定义了__setattr__、__getattr__等方法,意味着我们可以向操作Local对象属性的方式操作__storage__字典
try:
from greenlet import getcurrent as get_ident # 协程的唯一标识
except ImportError:
try:
from thread import get_ident # 线程的唯一标识
except ImportError:
from _thread import get_ident class Local(object):
__slots__ = ('__storage__', '__ident_func__') def __init__(self):
object.__setattr__(self, '__storage__', {}) # 这里不能直接使用self.__storage__ = {},因为会触发setattr
object.__setattr__(self, '__ident_func__', get_ident) def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None) # 删除__storage__中存放的线程相关数据(requestcontext) 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} def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)

LocalStack 类

Local类的作用只是用来存放数据,而LocalStack则提供了pop、top、push方法来操作Local中的数据。即Local中的数据是通过LocalStack来操作的。

  • push方法将一个RequestContext放到local.__storage__字典中stack对应的列表中
  • pop方法将RequestContext从列表中取出并从列表中删除
  • top方法用来获取RequestContext对象,并不会删除该对象

注意:每一次请求的生命周期中导入的request、session等其他flask全局对象,指向的都是同一个引用,也就是说在其他地方操作session都是操作的同一个session对象

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 def pop(self):
stack = getattr(self._local, 'stack', None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop() @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

源码流程分析

1. 请求进来后会首先执行app的__call__方法,在该方法内部调用的其实就是app.wsgi_app()方法

2. ctx = self.request_context(environ)方法本质上就是实例化一个RequestContext对象,并将请求的所有相关信息(WSGI environment)封装到RequestContext对象中,在实例化RequestContext对象的时候,其__init__方法中会干以下几件事儿

  •  2.1 初始化RequestContext,初始化app, request, session等属性
  •  2.2 调用requestcontext_obj.match_request(),该函数内部会匹配url_rule
class Flask:
def wsgi_app(self, environ, start_response): ctx = self.request_context(environ)
ctx.push() # 中间省略部分内容 def request_context(self, environ):
return RequestContext(self, environ) class 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 # 在self.push()中被赋值 self.match_request() def match_request(self):
try:
url_rule, self.request.view_args = \
self.url_adapter.match(return_rule=True)
self.request.url_rule = url_rule
except HTTPException as e:
self.request.routing_exception = e

2.3  request = app.request_class(environ)会将请求的所有相关信息分装成一个Request对象,并绑定到RequestContext对象的request属性上

class Flask:
request_class = Request # app.request_class其实就是Request

3.  ctx.push(),这个方法会将RequestContext对象通过LocalStack类push到Local.__storage__字典的列表中,

  获取当前请求对应的session数据并绑定RequestContext对象的session属性上

# globals.py

_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'
class RequestContext:
def push(self):
# 省略部分 _request_ctx_stack.push(self) # 把requestcontext保存到列表中 self.session = self.app.open_session(self.request) # 获取session数据并绑定到requestcontext对象的session属性中
if self.session is None:
self.session = self.app.make_null_session()

4.  当我们使用from flask import request, sesion的时候,就会触发LocalProxy类中相应对魔法方法,调用 _get_current_object() 函数,函数中调用 _lookup_req_object,通过LocalStack到Local到__storage__字典中

  对应的列表中的 RequestContext对象的request属性和session属性 对应的Request对象和SecureCookieSession对象取出

request = LocalProxy(partial(_lookup_req_object, 'request'))    # 获取requestcontext_obj.request
session = LocalProxy(partial(_lookup_req_object, 'session')) # 获取requestcontext_obj.session def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name) # 利用反射获取RequestContext对象的属性值

5. 请求结束时,调用RequestContext中调pop方法,通过Localstack将Local中__storage__字典内的数据pop掉

class LocalStack():  

    """"省略"""
def pop(self):
"""Removes the topmost item from the stack, will return the
old value or `None` if the stack was already empty.
"""
stack = getattr(self._local, 'stack', None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
class Local(object):
__slots__ = ('__storage__', '__ident_func__') def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident) def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)

应用上下文

应用上下文的原理和流程和管理上下文基本一致,他们分别创建了自己的一个Local类,在自己的Local类中为每一个线程创建类独立的存储数据的空间

上下文全局变量current_app, g, request, session等都是对象,我们可以在这些对象上通过属性绑定一些数据,在需要的时候再取出这些数据进行操作

from flask import Flask,request,g,current_app,

app = Flask(__name__)

@app.before_request
def before():
g.permission_code_list = ['list','add']
current_app.x = 123
request.name = "zhou" @app.route('/',methods=['GET',"POST"])
def index():
print(g.permission_code_list)
print(current_app.x)
print(request.name)
# ['list', 'add']
#
# zhou
return "index" if __name__ == '__main__':
app.run()

源码

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)
g = LocalProxy(partial(_lookup_app_object, 'g'))
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

 

 

Flask上下文管理及源码刨析的更多相关文章

  1. 30s源码刨析系列之函数篇

    前言 由浅入深.逐个击破 30SecondsOfCode 中函数系列所有源码片段,带你领略源码之美. 本系列是对名库 30SecondsOfCode 的深入刨析. 本篇是其中的函数篇,可以在极短的时间 ...

  2. MapReduce源码刨析

    MapReduce编程刨析: Map map函数是对一些独立元素组成的概念列表(如单词计数中每行数据形成的列表)的每一个元素进行指定的操作(如把每行数据拆分成不同单词,并把每个单词计数为1),用户可以 ...

  3. ConcurrentHashMap源码刨析(基于jdk1.7)

    看源码前我们必须先知道一下ConcurrentHashMap的基本结构.ConcurrentHashMap是采用分段锁来进行并发控制的. 其中有一个内部类为Segment类用来表示锁.而Segment ...

  4. HashMap源码刨析(面试必看)

    目录 1.Hash的计算规则? 2.HashMap是怎么形成环形链表的(即为什么不是线程安全)?(1.7中的问题) 3.JDK1.7和1.8的HashMap不同点? 4.HashMap和HashTab ...

  5. Java 源码刨析 - String

    [String 是如何实现的?它有哪些重要的方法?] String 内部实际存储结构为 char 数组,源码如下: public final class String implements java. ...

  6. Java 源码刨析 - HashMap 底层实现原理是什么?JDK8 做了哪些优化?

    [基本结构] 在 JDK 1.7 中 HashMap 是以数组加链表的形式组成的: JDK 1.8 之后新增了红黑树的组成结构,当链表大于 8 并且容量大于 64 时,链表结构会转换成红黑树结构,它的 ...

  7. Java 源码刨析 - 线程的状态有哪些?它是如何工作的?

    线程(Thread)是并发编程的基础,也是程序执行的最小单元,它依托进程而存在. 一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源,因此线程之间的切换更加节省资源.更加轻量化,也因 ...

  8. SSM-SpringMVC-04:SpringMVC深入浅出理解HandleMapping(源码刨析)

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 先从概念理解,从中央调度器,携带参数request,调度到HandleMapping处理器映射器,处理器映射器 ...

  9. form 源码刨析

    def clean_name(self) value = self.cleaned_data.get('name') if "金-瓶-梅" not in value: raise ...

随机推荐

  1. 【SSH进阶之路】Hibernate映射——一对一双向关联映射(六)

    上篇博文[SSH进阶之路]Hibernate映射--一对一单向关联映射(五),我们介绍了一对一的单向关联映射,单向是指仅仅能从人(Person)这端载入身份证端(IdCard),可是反过来.不能从身份 ...

  2. POJ 1321 棋盘问题(状态压缩DP)

    不总结的话, 同一个地方会 WA 到死 思路: 状态压缩 DP. 1. s 表示压缩状态, 若第 i 列放了棋子, 那么该列置 1, 否则该列置 0. 假如 s = 3(0x011) 那么表示棋盘的第 ...

  3. 在properties.xml中定义变量,在application.xml中取值问题

    如果为application.xml中的变量赋默认值,同时又在properties.xml中变量赋值,而加载后是取不到properties.xml中的值的问题. 解决这个问题需要加上黑体部分配置: & ...

  4. 查看内存使用情况:free

    free命令用于显示内存的使用情况,常见用法如下: [root@localhost ~]$ free # 以KB为单位显示内存使用情况 [root@localhost ~]$ free -m # 以M ...

  5. SGA内存的优化

    查看SGA有关的系统参数即介绍 SQL> show parameter sga NAME TYPE VALUE ------------------------------------ ---- ...

  6. HttpPost导包遇到的问题

    直接在当前项目 build.gradle文件修改如下 android { useLibrary 'org.apache.http.legacy' compileSdkVersion 24 buildT ...

  7. Android中的动画,选择器,样式和主题的使用

    一.动画: 1.动画的分类: 1).Tween动画:这种实现方式可以使视图组件移动.放大.缩小以及产生透明度的变化: 2).Frame动画:传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影. ...

  8. c++11实现l延迟调用(惰性求值)

    惰性求值 惰性求值一般用于函数式编程语言中,在使用延迟求值的时候,表达式不在它被绑定到变量之后就立即求值,而是在后面的某个时候求值.     可以利用c++11中的std::function, lam ...

  9. const T* 和 T* const

    使用c++的时候,经常会在 const int *p 和 int * const p这个地方迷惑.这里记录一下: const int *p  = int const *p //这里const后面的为* ...

  10. 1.node.js下载

    1.下载node.js http://nodejs.cn/ 2.下载git https://git-scm.com/download/win 3.安装npm npm install npm -g 使用 ...