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请求和应用上下文源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Flask上下文源码分析

Flask系列之源码分析

Flask源码之请求上下文和应用上下文

flask 请求处理流程及其源代码分析sessioncookie生成源码

flask请求上下文管理源码分析

Flask上下文管理源码分析