flask源码剖析--请求流程
想了解这篇里面的内容,请先去了解我另外一篇博客Flask上下文
在了解flask之前,我们需要了解两个小知识点
- 偏函数
import functools def func(a1,a2):
print(a1)
print(a2) #重新封装成一个 给前面参数加默认值 的函数
new_func = functools.partial(func, 666)
new_func(777)
- 面向对象 对象 + 会执行__add__方法
class Foo(object): def __init__(self, num):
self.num = num def __add__(self, other):
data = self.num + other.num
return Foo(data) obj1 = Foo(11)
obj2 = Foo(22)
v = obj1 + obj2
print(v.num)
- 拼接列表中的值
from itertools import chain v1 = [11,22,33]
v2 = [44,55,66] new = chain(v1, v2)
for item in new:
print(item) def f1(x):
return x + 1 func1_list = [f1, lambda x:x-1] def f2(x):
return x + 10 new_func_list = chain([f2], func1_list)
for func in new_func_list:
print(func)
- 强制调用私有变量 _类名__私有变量名
class Foo(object): def __init__(self):
self.name = 'alex'
self.__age = 18 def get_age(self):
return self.__age obj = Foo()
# print(obj.name)
# print(obj.get_age())
# 强制获取私有字段
print(obj._Foo__age)
为什么要了解这个几个点呢?这边先按下不说,分析过程中,自然就明白了
从哪里开始,那就要留心我们平时写代码了,flask程序运行起来主要app.run实现的
进入run函数中,代码最终执行了run_simple(host, port, self, **options),而run_simple执行时,第三参数加括号执行,也就是会执行self(flask对象)__calll__方法
__call__方法里,执行了self.wsgi_app(environ, start_response),而这里面的代码可以说是flask处理请求的核心代码
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
请求到来时
第一步
ctx = self.request_context(environ) 函数里执行了RequestContext(self, environ)
执行RequestContext类里的__init__方法
def __init__(self, app, environ, request=None):
self.app = app #app为flask对象
if request is None:
request = app.request_class(environ)
self.request = request #请求相关
self.url_adapter = app.create_url_adapter(self.request) #url映射
self.flashes = None #闪现相关
self.session = None #session相关
- 注意此时self为RequestContext对象,也就是赋值给ctx变量的对象
- __init__方法主要把flask对象,生成请求对象并把请求和闪现,session...相关的东西封装在ctx里
第二步
- ctx.push() 执行RequestContext里的push方法,函数里最终执行了_request_ctx_stack.push(self)
执行的是LocalStack的push方法,并把RequestContext(ctx)对象为参数传了进来
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return r
- 刚开始进入stack是没有值的,所以会执行self._local.stack = rv = [],等同把空列表同时赋值给stack和rv
- self._local.stack = [],本质上会执行self._local对象里的__setattr__方法,也就是Local类的
#其中name为stack,value为[]
def __setattr__(self, name, value):
ident = self.__ident_func__() #线程或协程唯一标识
storage = self.__storage__ #__init__方法赋值为{}
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
3. 等同把RequestContext对象放到Local的这么一个字典中
storage = {
唯一ID:{
stack:[RequestContext对象,]
},
唯一ID:{
stack:[RequestContext对象,]
}
}
请求结束时
- 在wsgi_app函数中,在try代码中无非就是url映射,找到视图函数并执行,执行完后就需要到存储字典里把相关request数据移除掉,而在finally最终会执行这么一句代码ctx.auto_pop(error)--error有错误时就是错误信息没有就是None,也就是执行RequestContext类里的auto_pop方法
- else中执行了self.pop(exc) self为RequestContext对象,pop里最终执行_request_ctx_stack.pop(),而_request_ctx_stack则是LocalStack对象,也就是执行了LocalStack类的pop方法
stack = getattr(self._local, 'stack', None) #stack 类似这么个列表[RequestContext对象,]
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop() #列表pop进行删除
请求处理中
- 找到视图函数并执行请求
- print(request),打印对象,会执行类里的__str__方法,那这个request是哪个类的呢,要知道这个,就需要知道在导入时,是从哪导的
from flask import Flask,request
- 也就是执行了下面这句代码,也许你会觉得这个partial怎么似曾相识啊,这个就是偏函数,把request字符串当默认值传入,并且还是返回函数,所以这句代码就是实例化LocalProxy对象,并把一个偏函数传了进去,此时会去执行LocalProxy的__init__,所以说在导入的request对象,本质是LocalProxy对象
request = LocalProxy(partial(_lookup_req_object, 'request'))
- 执行LocalProxy的__init__方法时,有这么一句代码,其中_LocalProxy__local是不是感觉有在哪见过,对没错,这就是强制调用私有变量,本质上就是做了__local = 偏函数,local是传进来的偏函数
object.__setattr__(self, '_LocalProxy__local', local)
- 回到视图函数进行打印request则会执行LocalProxy里的__str__方法,也就是下面这句
__str__ = lambda x: str(x._get_current_object())
- 在_get_current_object函数里,进入if中执行__local加括号,也就是执行偏函数
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__)
- 而在_get_current_object主要做了这么一件事,提取偏函数并执行,也就是执行下面这个函数
def _lookup_req_object(name): #此时的name为request字符串
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
- _request_ctx_stack.top,执行LocalStack的top,最终返回就是字典存储的RequestContext对象
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
- 所以最终getattr(top, name),是去RequestContext中获取request数据的
- request.method,执行LocalProxy的__getattr__方法
- 还是执行_get_current_object方法,获取偏函数_lookup_req_object执行,提取request数据
上下文总结:
threading.Local和Flask自定义Local对象
--请求到来
- ctx = 封装RequestContext(request, session)
- ctx放到Local中
--执行视图时
- 导入request
- print(request) --> LocalProxy对象的__str__
- request.method --> LocalProxy对象的__getattr__
- request + 1 --> LocalProxy对象的__add__
- 调用 _lookup_req_object函数:去local中将requestContext想获取到,再去requestContext中获取request或session
-- 请求结束
- ctx.auto_pop()
- ctx从local中移除
了解整个请求流程源码后,其实你也可以这么做
from flask.globals import _request_ctx_stack
from functools import partial def _lookup_req_object(name):
# name = request
# top= ctx
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('不存在')
# return ctx.request
return getattr(top, name) class Foo(object):
def __init__(self):
self.xxx = 123
self.ooo = 888 req = partial(_lookup_req_object,'xxx')
xxx = partial(_lookup_req_object,'ooo') # 当前求刚进来时
_request_ctx_stack.push(Foo()) # 使用
# obj = _request_ctx_stack.top
# obj.xxx
v1 = req()
print(v1)
v2 = xxx()
print(v2) # 请求终止,将local中的值移除
_request_ctx_stack.pop()
APP上下文
由于flask版本的区别,有一部分数据被分离到APP上下文中,而其原理和请求上下文是一样的
- 我们再次看到app.wsgi_app源码中这句ctx.push(),函数里会有这么几句
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是RequestContext对象,即ctx,self.app就是封装在里面的flask对象,再看下app_context方法中干了啥
- 返回的是一个AppContext,并把flask对象传入了进去,并执行AppContext的__init__方法,封装了flask对象给app,还封装了一个g对象,对于这个对象,你就可以理解为一个存储数据的字典
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
为了方便你理解这个g中主要存储什么数据,这里不妨举个例子:比如你在请求前有个数据要传递给视图函数使用,你会怎么搞了,你可能会想,直接通过request.xxx=yyy赋值就可以了,这样做是可以的,但是为了更好避免名字重了,你用这个g来进行存储,这里需要注意的是:它主要保存的是一个请求周期的值
from flask import Flask,request,g app = Flask(__name__) @app.before_request
def before():
g.permission_code_list = ['list','add'] @app.route('/',methods=['GET',"POST"])
def index():
print(g.permission_code_list)
return "index" if __name__ == '__main__':
app.run()
- app_ctx.push()-->_app_ctx_stack.push(self),其中_app_ctx_stack又是一个LocalStack对象,执行它里面的push,会把app_ctx(AppContext对象)放到_app_ctx_stack下的local,这里会注意到:无论多少个线程,会创建请求上下文和APP上下文两个local对象进行存储数据
- 上面是请求到来时,请求结束时,也会进行删除,和请求上下文是一样,可以通过ctx.auto_pop(error)一步一步看下去,最终会执行_app_ctx_stack.pop(),也就是LocalStack的pop
- 请求过程中,比如打印g对象,还是执行LocalProxy的__str__方法,最终还是执行偏函数,不过这里偏函数换成了_lookup_app_object,也是去local中获取到AppContext对象,并获取到g对象
多APP应用
我们已经学过了蓝图,url过来,经过app分发给蓝图进行处理
接下来的内容,则可以通过不同的APP来处理不同的url
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
from flask import Flask, current_app app1 = Flask('app01') app2 = Flask('app02') @app1.route('/index')
def index():
return "app01" @app2.route('/index2')
def index2():
return "app2" # http://www.oldboyedu.com/index
# http://www.oldboyedu.com/sec/index2
dm = DispatcherMiddleware(app1, {
'/sec': app2,
}) if __name__ == "__main__":
app2.__call__
run_simple('localhost', 5000, dm)
对上面这段代码的实现原理,看了源码后,你才会发现,原来如此简单
- 先看到run_simple,最终会执行第三参数+(),也就是dm()
- dm是一个DispatcherMiddleware对象,里面封装了起始app以及其他app和url的映射关系
def __init__(self, app, mounts=None):
self.app = app
self.mounts = mounts or {}
- dm()会执行DispatcherMiddleware的__call__方法
def __call__(self, environ, start_response):
script = environ.get('PATH_INFO', '') #获取url信息,比如/sec/index2
path_info = ''
while '/' in script:
if script in self.mounts: #判断当前url在不在映射关系里
app = self.mounts[script] #在就获取url对应的app对象,退出循环
break
#分割后 script='/sec' last_itme='index2'
script, last_item = script.rsplit('/', 1) #如果不在映射关系里,从右进行分割一次
path_info = '/%s%s' % (last_item, path_info) #path_info="/index2" 用于去APP下面找视图函数
else:
app = self.mounts.get(script, self.app) #如果都没有匹配到,那就获取 起始app
#最后把处理好的路径信息重新封装到environ中
original_script_name = environ.get('SCRIPT_NAME', '')
environ['SCRIPT_NAME'] = original_script_name + script
environ['PATH_INFO'] = path_info
return app(environ, start_response)
- 源码最后执行了app(environ, start_response),如果是/sec/index2,那么此时的app就是app2,是一个flask对象,加括号,会执行里面的__call__,从这里开始又和单app情况下执行流程是一样的
最后,多app情况下,local下存储结构是咋样的呢?
- 和单app的场景是一样的,同时通过线程和协程唯一标识进行存储的
- 在local下获取值和设置值的,不存在通过类似app唯一标识进行操作,只通过线程和协程唯一标识
- 在上述代码中,如果导入request,两个app是暴露在同一全局变量下的,request不对app进行区分
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 #name -->'stack'
except KeyError:
storage[ident] = {name: value}
可能大家一直有个疑问,那就是local存储中存储APPcontent对象为什么要用栈呢?
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
正常请求流程中,是不会出现栈里有两个对象的,但是测试在进行离线脚本测试时,就有可能有两个呢?
想了解清楚,这里还是了解一下面向对象的一个知识点
class SQLHelper(object): def open(self):
pass def fetch(self,sql):
pass def close(self):
pass def __enter__(self):
self.open()
return self def __exit__(self, exc_type, exc_val, exc_tb):
self.close() # obj = SQLHelper()
# obj.open()
# obj.fetch('select ....')
# obj.close() with SQLHelper() as obj: # 自动调用类中的__enter__方法, obj就是__enter__返回值
obj.fetch('xxxx')
# 当执行完毕后,自动调用类 __exit__ 方法
当测试脚本中,存在with嵌套时,栈中就可能有多个对象
from flask import Flask,current_app,globals,_app_ctx_stack app1 = Flask('app01')
app1.debug = False # 用户/密码/邮箱
# app_ctx = AppContext(self):
# app_ctx.app
# app_ctx.g app2 = Flask('app02')
app2.debug = True # 用户/密码/邮箱
# app_ctx = AppContext(self):
# app_ctx.app
# app_ctx.g with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
# {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
print(_app_ctx_stack._local.__storage__)
print(current_app.config['DEBUG']) with app2.app_context():
# {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
print(_app_ctx_stack._local.__storage__)
print(current_app.config['DEBUG']) #退出时,执行__exit__方法,执行pop,移除栈里最后的一个对象
print(current_app.config['DEBUG'])
flask源码剖析--请求流程的更多相关文章
- flask源码剖析系列(系列目录)
flask源码剖析系列(系列目录) 01 flask源码剖析之werkzurg 了解wsgi 02 flask源码剖析之flask快速使用 03 flask源码剖析之threading.local和高 ...
- 08 Flask源码剖析之flask拓展点
08 Flask源码剖析之flask拓展点 1. 信号(源码) 信号,是在flask框架中为我们预留的钩子,让我们可以进行一些自定义操作. pip3 install blinker 2. 根据flas ...
- 浅谈flask源码之请求过程
更新时间:2018年07月26日 09:51:36 作者:Dear. 我要评论 这篇文章主要介绍了浅谈flask源码之请求过程,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随 ...
- 07 flask源码剖析之用户请求过来流程
07 Flask源码之:用户请求过来流程 目录 07 Flask源码之:用户请求过来流程 1.创建ctx = RequestContext对象 2. 创建app_ctx = AppContext对象 ...
- flask 源码剖析
flask 上下文管理源码流程及涉及的部分技术点 [flask源码梳理]之一 偏函数_mro [flask源码梳理]之二 面向对象中__setattr__ [flask源码梳理]之三 Local ...
- Flask源码剖析详解
1. 前言 本文将基于flask 0.1版本(git checkout 8605cc3)来分析flask的实现,试图理清flask中的一些概念,加深读者对flask的理解,提高对flask的认识.从而 ...
- 04 flask源码剖析之LocalStack和Local对象实现栈的管理
04 LocalStack和Local对象实现栈的管理 目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于l ...
- 06 flask源码剖析之路由加载
06 Flask源码之:路由加载 目录 06 Flask源码之:路由加载 1.示例代码 2.路由加载源码分析 1.示例代码 from flask import Flask app = Flask(__ ...
- 05 flask源码剖析之配置加载
05 Flask源码之:配置加载 目录 05 Flask源码之:配置加载 1.加载配置文件 2.app.config源码分析 3.from_object源码分析 4. 总结 1.加载配置文件 from ...
随机推荐
- Spring-Condition设置
为了满足不同条件下生成更为合适的bean,可以使用condition配置其条件.假如有一个bean,id为magicBean,只有当其具有magic属性时才生成,方法如下: javaConfig模式: ...
- 教您如何在Word的mathtype加载项中修改章节号
在MathType数学公式编辑器中,公式编号共有五部分内容:分别是章编号(Chapter Number).节编号(Section Number).公式编号(Equation Number).括号(En ...
- 提高Web性能的前端优化技巧总结
- hive与hbase的联系与区别
hive与hbase的联系与区别: 共同点: 1.hbase与hive都是架构在hadoop之上的.都是用hadoop作为底层存储. 他们的底层是要通过mapreduce分布式计算的,hbase.hi ...
- Tomcat在Linux下的安装与配置
一.安装配置JDK 1.官网下载JDK1.7 mkdir /usr/java cd /uar/java wget http://download.oracle.com/otn/java/jdk/7u8 ...
- C文件流
在Linux系统中,系统默认认为每个进程打开了3个文件,即每个进程默认可以操作3 个流,即标准输入了流(/dev/stdin),标准输出流(/dev/stdout),标准错误输出流(/dev/stde ...
- PyQt4 菜单栏 + 工具栏 + 状态栏 + 中心部件 生成一个文本编辑部件示例
我们将创建一个菜单栏.一个工具栏.一个状态栏和一个中心部件. #!/usr/bin/python # -*- coding:utf-8 -*- import sys from PyQt4 import ...
- poj_3168 平面扫描
题目大意 给定平面上N个矩形的位置(给出矩形的左下角和右上角的坐标),这些矩形有些会有重叠,且重叠只会出现矩形的边重合全部或部分,矩形的顶点重合,而不会出现一个矩形的顶点位于另一个矩形的内部. ...
- 广义表操作 (ava实现)——广义表深度、广义表长度、打印广义表信息
广义表是对线性表的扩展——线性表存储的所有的数据都是原子的(一个数或者不可分割的结构),且所有的数据类型相同.而广义表是允许线性表容纳自身结构的数据结构. 广义表定义: 广义表是由n个元素组成的序列: ...
- MQTT-SN协议乱翻之简要介绍
前言 这一段时间在翻看MQTT-SN的协议,对针对不依赖于TCP传输的MQTT协议十分感兴趣,总是再想着这货到底是怎么定义的.一系列文章皆有MQTT-SN 1.2协议所拼装组成,原文档地址: MQTT ...