Flask请求和应用上下文源码分析
Posted fengff
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask请求和应用上下文源码分析相关的知识,希望对你有一定的参考价值。
flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递。
flask是如何做的呢?
1:本地线程,保证即使是多个线程,自己的值也是互相隔离
1 import threading 2 3 local_values = threading.local() 4 5 6 def func(num): 7 local_values.name = num 8 import time 9 time.sleep(1) 10 print(local_values.name, threading.current_thread().name) 11 12 13 for i in range(20): 14 th = threading.Thread(target=func, args=(i,), name=‘线程%s‘ % i) 15 th.start()
2:自定义threading.local
1 """ 2 { 3 1368:{} 4 } 5 6 7 8 """ 9 import threading 10 try: 11 from greenlet import getcurrent as get_ident # 协程 12 except ImportError: 13 try: 14 from thread import get_ident 15 except ImportError: 16 from _thread import get_ident # 线程 17 18 19 class Local(object): 20 def __init__(self): 21 self.storage = {} 22 self.get_ident = get_ident 23 24 def set(self,k,v): 25 ident = self.get_ident() 26 origin = self.storage.get(ident) 27 if not origin: 28 origin = {k:v} 29 else: 30 origin[k] = v 31 self.storage[ident] = origin 32 33 def get(self,k): 34 ident = self.get_ident() 35 origin = self.storage.get(ident) 36 if not origin: 37 return None 38 return origin.get(k,None) 39 40 local_values = Local() 41 42 43 def task(num): 44 local_values.set(‘name‘,num) 45 import time 46 time.sleep(1) 47 print(local_values.get(‘name‘), threading.current_thread().name) 48 49 50 for i in range(20): 51 th = threading.Thread(target=task, args=(i,),name=‘线程%s‘ % i) 52 th.start()
升级版
1 import threading 2 try: 3 from greenlet import getcurrent as get_ident # 协程 4 except ImportError: 5 try: 6 from thread import get_ident 7 except ImportError: 8 from _thread import get_ident # 线程 9 10 11 class Local(object): 12 13 def __init__(self): 14 object.__setattr__(self, ‘__storage__‘, {}) 15 object.__setattr__(self, ‘__ident_func__‘, get_ident) 16 17 18 def __getattr__(self, name): 19 try: 20 return self.__storage__[self.__ident_func__()][name] 21 except KeyError: 22 raise AttributeError(name) 23 24 def __setattr__(self, name, value): 25 ident = self.__ident_func__() 26 storage = self.__storage__ 27 try: 28 storage[ident][name] = value 29 except KeyError: 30 storage[ident] = {name: value} 31 32 def __delattr__(self, name): 33 try: 34 del self.__storage__[self.__ident_func__()][name] 35 except KeyError: 36 raise AttributeError(name) 37 38 39 local_values = Local() 40 41 42 def task(num): 43 local_values.name = num 44 import time 45 time.sleep(1) 46 print(local_values.name, threading.current_thread().name) 47 48 49 for i in range(20): 50 th = threading.Thread(target=task, args=(i,),name=‘线程%s‘ % i) 51 th.start()
说明解释:
1 - threading.local对象,用于为每个线程开辟一块空间来保存它独有的值。 2 3 - 源码(request) 4 - 情况一:单进程单线程,基于全局变量做。 5 - 情况二:单进程多线程,threading.local对象。 6 - 情况二:单进程单线程(多个协程),threading.local对象做不到。 7 8 - 决定: 9 - 以后不支持协程:threading.local对象。 10 - 支持:自定义类似threading.local对象(支持协程) 11 - 自定义类似threading.local对象 12 PS: 13 a. 14 object.__setattr__(self, ‘storage‘, {}) 15 self.storage = {} 16 b. 17 对象.xx 18 def __setattr__(self, key, value): 19 print(key,value)
3:请求上下文原理
1 from functools import partial 2 from flask.globals import LocalStack, LocalProxy 3 4 ls = LocalStack() 5 6 7 class RequestContext(object): 8 def __init__(self, environ): 9 self.request = environ 10 11 12 def _lookup_req_object(name): 13 top = ls.top 14 if top is None: 15 raise RuntimeError(ls) 16 return getattr(top, name) 17 18 19 session = LocalProxy(partial(_lookup_req_object, ‘request‘)) 20 21 ls.push(RequestContext(‘c1‘)) # 当请求进来时,放入 22 print(session) # 视图函数使用 23 print(session) # 视图函数使用 24 ls.pop() # 请求结束pop 25 26 27 ls.push(RequestContext(‘c2‘)) 28 print(session) 29 30 ls.push(RequestContext(‘c3‘)) 31 print(session)
4:请求上下文源码
1 from greenlet import getcurrent as get_ident 2 3 4 def release_local(local): 5 local.__release_local__() 6 7 8 class Local(object): 9 __slots__ = (‘__storage__‘, ‘__ident_func__‘) 10 11 def __init__(self): 12 # self.__storage__ = {} 13 # self.__ident_func__ = get_ident 14 object.__setattr__(self, ‘__storage__‘, {}) 15 object.__setattr__(self, ‘__ident_func__‘, get_ident) 16 17 def __release_local__(self): 18 self.__storage__.pop(self.__ident_func__(), None) 19 20 def __getattr__(self, name): 21 try: 22 return self.__storage__[self.__ident_func__()][name] 23 except KeyError: 24 raise AttributeError(name) 25 26 def __setattr__(self, name, value): 27 ident = self.__ident_func__() 28 storage = self.__storage__ 29 try: 30 storage[ident][name] = value 31 except KeyError: 32 storage[ident] = {name: value} 33 34 def __delattr__(self, name): 35 try: 36 del self.__storage__[self.__ident_func__()][name] 37 except KeyError: 38 raise AttributeError(name) 39 40 41 class LocalStack(object): 42 def __init__(self): 43 self._local = Local() 44 45 def __release_local__(self): 46 self._local.__release_local__() 47 48 def push(self, obj): 49 """Pushes a new item to the stack""" 50 rv = getattr(self._local, ‘stack‘, None) 51 if rv is None: 52 self._local.stack = rv = [] 53 rv.append(obj) 54 return rv 55 56 def pop(self): 57 """Removes the topmost item from the stack, will return the 58 old value or `None` if the stack was already empty. 59 """ 60 stack = getattr(self._local, ‘stack‘, None) 61 if stack is None: 62 return None 63 elif len(stack) == 1: 64 release_local(self._local) 65 return stack[-1] 66 else: 67 return stack.pop() 68 69 @property 70 def top(self): 71 """The topmost item on the stack. If the stack is empty, 72 `None` is returned. 73 """ 74 try: 75 return self._local.stack[-1] 76 except (AttributeError, IndexError): 77 return None 78 79 80 stc = LocalStack() 81 82 stc.push(123) 83 v = stc.pop() 84 85 print(v)
5:个人源码剖析
a.偏函数
1 import functools 2 3 def func(a1): 4 print(a1) 5 6 7 new_func = functools.partial(func,666) 8 9 new_func() 10 11 说明:将666这个参数当做func函数的第一个参数
b.第一阶段
1 1.当携带这用户的请求过来之后,首先会执行app.run,会执行 run_simple(host, port, self, **options),因为run方法是由app调用的,app又是Flask的对象,因此当执行了app.run()之后就会调用Flask类中的__call__方法 2 2.进入app.__call__拿到return self.wsgi_app(environ, start_response) 3 3.进入app.wsgi_app之后就会执行:ctx = self.request_context(environ) 4 app.request_context(environ)会返回 return RequestContext(self, environ);然后执行__init__方法得到request = app.request_class(environ);最后将请求的信息放入request中。经过上面的操作,得出ctx就是携带有请求信息的request_context对象,ctx里面具有ctx.app,ctx.request,ctx.session 5 4.然后执行ctx.push() 6 进入push()里面找到 _request_ctx_stack.push(self),这里面的self指的是ctx对象,_request_ctx_stack是全局变量,_request_ctx_stack = LocalStack(),_request_ctx_stack 是LocalStack这个类的对象;通过def push(self, obj)这个方法将ctx这个对象存入threadinglocal中,给ctx这个对象分配专属与他的一块内存空间。
c.第二阶段
1 如果需要打印print(request),源码是如何执行的呢? 2 进入request 3 1.request = LocalProxy(partial(_lookup_req_object, ‘request‘)) 4 这个里面:偏函数=partial(_lookup_req_object, ‘request‘) 5 首先执行LocalProxy这个类实例化执行__init__方法: 6 def __init__(self, local, name=None): 7 object.__setattr__(self, ‘_LocalProxy__local‘, local) 8 在这里:_LocalProxy__local其实就是强制获取私有字段等价于 9 self._local = local 10 2.再执行__str__方法: 11 __str__ = lambda x: str(x._get_current_object()) 12 3.def _get_current_object(self): 13 if not hasattr(self.__local, ‘__release_local__‘): 14 return self.__local() 15 try: 16 return getattr(self.__local, self.__name__) 17 返回return self.__local(),执行偏函数 18 def _lookup_req_object(name): 19 top = _request_ctx_stack.top 20 if top is None: 21 raise RuntimeError(_request_ctx_err_msg) 22 return getattr(top, name) 23 4.从top = _request_ctx_stack.top中去拿开始存入的ctx对象,然后再从ctx对象中去拿request: return self._local.stack[-1] 24 5.通过getattr(top, name)==getattr(ctx对象,request) 25 26 如果是打印print(request.method) 27 其实就是多了一步,再执行__str__方法之前执行def __getattr__(self, name)方法 28 29 30 代码示例展示: 31 from flask import Flask,request,session,g,current_app 32 33 app = Flask(__name__) 34 35 @app.route(‘/‘,methods=[‘GET‘,"POST"]) 36 def index(): 37 # request是 LocalProxy 的对象 38 print(request) # LocalProxy.__str__ --> str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.request 39 request.method # LocalProxy.__getattr__ --> 40 # str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.request 41 # getattr(self._get_current_object(), name) --> ctx.request.method 42 43 request.path # ctx.request.path 44 45 print(session) # LocalProxy.__str__ --> str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.session 46 47 48 print(g) # 执行g对象的__str__ 49 return "index" 50 51 52 if __name__ == ‘__main__‘: 53 app.__call__ 54 app.wsgi_app 55 app.wsgi_app 56 app.request_class 57 app.run()
d.第三阶段
1 # 寻找视图函数并执行,获取返回值 2 response = self.full_dispatch_request() 3 4 1.先执行before_first_request 5 self.try_trigger_before_first_request_functions() 6 7 2.触发request_started信号,request_started 8 request_started.send(self) 9 补充:信号: 10 from flask import Flask, signals, render_template 11 12 app = Flask(__name__) 13 14 15 # 往信号中注册函数 16 def func(*args, **kwargs): 17 print(‘触发型号‘, args, kwargs) 18 19 20 signals.request_started.connect(func) 21 22 23 # 触发信号: signals.request_started.send() 24 25 @app.before_first_request 26 def before_first1(*args, **kwargs): 27 pass 28 29 30 @app.before_first_request 31 def before_first2(*args, **kwargs): 32 pass 33 34 35 @app.before_request 36 def before_first3(*args, **kwargs): 37 pass 38 39 40 @app.route(‘/‘, methods=[‘GET‘, "POST"]) 41 def index(): 42 print(‘视图‘) 43 return render_template(‘index.html‘) 44 45 46 if __name__ == ‘__main__‘: 47 app.wsgi_app 48 app.run() 49 50 3.再执行def preprocess_request(self):中的before_request 51 52 4.# 执行视图函数dispatch_request-->def render_template(template_name_or_list, **context):-->before_render_template.send(app, template=template, context=context)-->template_rendered.send(app, template=template, context=context) 53 rv = self.dispatch_request() 54 55 5. 执行self.finalize_request(rv)--> response = self.process_response(response)-->self.session_interface.save_session(self, ctx.session, response) 56 57 6.执行after_request 58 59 7.执行信号request_finished.send(self, response=response)
e.第四阶段
1 ctx.auto_pop,将请求信息从threadinglocal中清除 2 1.ctx.auto_pop -->self.pop(exc)-->_request_ctx_stack.pop() 3 4 2._request_ctx_stack = LocalStack()-->return stack.pop()
6.应用上下文原理(跟请求上下文的原理几乎一致)
1 通过app_ctx.push()将app_ctx对象存储到threadinglocal中,分配一块独有的内存空间,app_ctx对象中具有:app_ctx.app与app_ctx.g 2 3 1.app_ctx.g是存储变量用的 4 示例代码: 5 from flask import Flask,request,g 6 7 app = Flask(__name__) 8 9 @app.before_request 10 def before(): 11 g.permission_code_list = [‘list‘,‘add‘] 12 13 14 @app.route(‘/‘,methods=[‘GET‘,"POST"]) 15 def index(): 16 print(g.permission_code_list) 17 return "index" 18 19 20 if __name__ == ‘__main__‘: 21 app.run()
补充:
a:离线脚本示例
1 """ 2 需求:不用数据库连接池,显示数据库连接 3 """ 4 class SQLHelper(object): 5 6 def open(self): 7 pass 8 9 def fetch(self,sql): 10 pass 11 12 def close(self): 13 pass 14 15 def __enter__(self): 16 self.open() 17 return self 18 19 def __exit__(self, exc_type, exc_val, exc_tb): 20 self.close() 21 22 23 # obj = SQLHelper() 24 # obj.open() 25 # obj.fetch(‘select ....‘) 26 # obj.close() 27 28 29 with SQLHelper() as obj: # 自动调用类中的__enter__方法, obj就是__enter__返回值 30 obj.fetch(‘xxxx‘) 31 # 当执行完毕后,自动调用类 __exit__ 方法
1 from flask import Flask,current_app,globals,_app_ctx_stack 2 3 app1 = Flask(‘app01‘) 4 app1.debug = False # 用户/密码/邮箱 5 # app_ctx = AppContext(self): 6 # app_ctx.app 7 # app_ctx.g 8 9 app2 = Flask(‘app02‘) 10 app2.debug = True # 用户/密码/邮箱 11 # app_ctx = AppContext(self): 12 # app_ctx.app 13 # app_ctx.g 14 15 16 17 with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local 18 # {<greenlet.greenlet object at 0x00000000036E2340>: {‘stack‘: [<flask.ctx.AppContext object at 0x00000000037CA438>]}} 19 print(_app_ctx_stack._local.__storage__) 20 print(current_app.config[‘DEBUG‘]) 21 22 with app2.app_context(): 23 # {<greenlet.greenlet object at 0x00000000036E2340>: {‘stack‘: [<flask.ctx.AppContext object at 0x00000000037CA438> ]}} 24 print(_app_ctx_stack._local.__storage__) 25 print(current_app.config[‘DEBUG‘]) 26 27 print(current_app.config[‘DEBUG‘])
7.总结
1 1. 面向对象私有 2 class Foo(object): 3 4 def __init__(self): 5 self.name = ‘alex‘ 6 self.__age = 18 7 8 def get_age(self): 9 return self.__age 10 11 obj = Foo() 12 # print(obj.name) 13 # print(obj.get_age()) 14 # 强制获取私有字段 15 print(obj._Foo__age) 16 17 2. 谈谈Flask上下文管理 18 - 与django相比是两种不同的实现方式。 19 - django/tornado是通过传参数形式 20 - flask是通过上下文管理 21 两种都可以实现,只不过试下方式不一样。 22 - 上下文管理: 23 - threading.local/Local类,其中创建了一个字典{greelet做唯一标识:存数据} 保证数据隔离 24 - 请求进来: 25 - 请求相关所有数据封装到了RequestContext中。 26 - 再讲RequestContext对象添加到Local中(通过LocalStack将对象添加到Local对象中) 27 - 使用,调用request 28 - 调用此类方法 request.method、print(request)、request+xxx 会执行LocalProxy中对应的方法 29 - 函数 30 - 通过LocalStack去Local中获取值。 31 - 请求终止 32 - 通过LocalStack的pop方法 Local中将值异常。 33 34 3.上下文 35 a. 请求上下文 36 - request 37 - session 38 b. 应用上下文 39 40 41 请求流程: 42 _request_ctx_stack.local = { 43 44 } 45 46 _app_ctx_stack.local = { 47 48 } 49 50 51 3.1. 请求到来 ,有人来访问 52 # 将请求相关的数据environ封装到了RequestContext对象中 53 # 再讲对象封装到local中(每个线程/每个协程独立空间存储) 54 # ctx.app # 当前APP的名称 55 # ctx.request # Request对象(封装请求相关东西) 56 # ctx.session # 空 57 _request_ctx_stack.local = { 58 唯一标识:{ 59 "stack":[ctx, ] 60 }, 61 唯一标识:{ 62 "stack":[ctx, ] 63 }, 64 } 65 66 67 # app_ctx = AppContext对象 68 # app_ctx.app 69 # app_ctx.g 70 71 _app_ctx_stack.local = { 72 唯一标识:{ 73 "stack":[app_ctx, ] 74 }, 75 唯一标识:{ 76 "stack":[app_ctx, ] 77 }, 78 } 79 80 3.2. 使用 81 from flask import request,session,g,current_app 82 83 print(request,session,g,current_app) 84 85 都会执行相应LocalProxy对象的 __str__ 86 87 current_app = LocalProxy(_find_app) 88 request = LocalProxy(partial(_lookup_req_object, ‘request‘)) 89 session = LocalProxy(partial(_lookup_req_object, ‘session‘)) 90 91 current_app = LocalProxy(_find_app) 92 g = LocalProxy(partial(_lookup_app_object, ‘g‘)) 93 94 3.3. 终止,全部pop 95 96 问题1:多线程是如何体现? 97 问题2:flask的local中保存数据时,使用列表创建出来的栈。为什么用栈? 98 - 如果写web程序,web运行环境;栈中永远保存1条数据(可以不用栈)。 99 - 写脚本获取app信息时,可能存在app上下文嵌套关系。 100 from flask import Flask,current_app,globals,_app_ctx_stack 101 102 app1 = Flask(‘app01‘) 103 app1.debug = False # 用户/密码/邮箱 104 # app_ctx = AppContext(self): 105 # app_ctx.app 106 # app_ctx.g 107 108 app2 = Flask(‘app02‘) 109 app2.debug = True # 用户/密码/邮箱 110 # app_ctx = AppContext(self): 111 # app_ctx.app 112 # app_ctx.g 113 114 115 116 with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local 117 # {<greenlet.greenlet object at 0x00000000036E2340>: {‘stack‘: [<flask.ctx.AppContext object at 0x00000000037CA438>]}} 118 print(_app_ctx_stack._local.__storage__) 119 print(current_app.config[‘DEBUG‘]) 120 121 with app2.app_context(): 122 # {<greenlet.greenlet object at 0x00000000036E2340>: {‘stack‘: [<flask.ctx.AppContext object at 0x00000000037CA438> ]}} 123 print(_app_ctx_stack._local.__storage__) 124 print(current_app.config[‘DEBUG‘]) 125 126 print(current_app.config[‘DEBUG‘]) 127 128 3.3. 多app应用 129 130 from werkzeug.wsgi import DispatcherMiddleware 131 from werkzeug.serving import run_simple 132 from flask import Flask, current_app 133 134 app1 = Flask(‘app01‘) 135 136 app2 = Flask(‘app02‘) 137 138 139 140 @app1.route(‘/index‘) 141 def index(): 142 return "app01" 143 144 145 @app2.route(‘/index2‘) 146 def index2(): 147 return "app2" 148 149 # http://www.oldboyedu.com/index 150 # http://www.oldboyedu.com/sec/index2 151 dm = DispatcherMiddleware(app1, { 152 ‘/sec‘: app2, 153 }) 154 155 if __name__ == "__main__": 156 run_simple(‘localhost‘, 5000, dm) 157 158 4.信号 159 a. before_first_request 160 b. 触发 request_started 信号 161 c. before_request 162 d. 模板渲染 163 渲染前的信号 before_render_template.send(app, template=template, context=context) 164 rv = template.render(context) # 模板渲染 165 渲染后的信号 template_rendered.send(app, template=template, context=context) 166 e. after_request 167 f. session.save_session() 168 g. 触发 request_finished信号 169 170 如果上述过程出错: 171 触发错误处理信号 got_request_exception.send(self, exception=e) 172 173 h. 触发信号 request_tearing_down 174 175 5.面向对象 176 - 封装 177 class Foo: 178 def __init__(self): 179 self.age = 123 180 self.nmame = ‘ssdf‘ 181 182 class Bar: 183 def __init__(self): 184 self.xx = 111 185 186 187 188 class Base: 189 def __init__(self): 190 self.f = Foo() 191 self.x = Bar() 192 - 某个值+括号 193 - 函数/方法 194 - 类 195 - 对象 196 197 - 特殊的双下划线方法: 198 __new__ 199 __call__ 200 __str__ 201 __setattr__ 202 __setitem__ 203 __enter__ 204 __exit__ 205 __add__ 206 207 PS: Flask的LocalProxy中全部使用。 208 209 - 强制调用私有字段 210 - 派生类中无法调用基类私有字段
以上是关于Flask请求和应用上下文源码分析的主要内容,如果未能解决你的问题,请参考以下文章