阅读目录:
- 引入
- 源码分析
引入:
为什么设计上下文这样的机制?
就是保证多线程环境下,实现线程之间的隔离.
在了解flask上下文机制之前,我们先了解下线程的数据安全.
线程安全:
如上代码段,在1s内开启20个线程,执行add_num(),结果foo.num都为 19,说明线程间数据是不隔离的.
那么,如何保证线程间数据隔离呢? 有一种 threading.local 方法
Thread Local
threading.local 在多线程操作时,为每一个线程开辟一个空间来保存它的值,使得线程之间的值互不影响.
import time
from threading import Thread,local
class Foo(local):
num = 0
foo = Foo()
def add_num(i):
foo.num = i
time.sleep(1)
print(i,foo.num)
for i in range(20):
task = Thread(target=add_num,args=(i,))
task.start()
也可以自定义一个线程安全: 定义一个全局字典,key为当前线程的线程ID,value为具体的值
import copy
import time
from threading import Thread,get_ident
class Foo():
num = 0
foo = Foo()
dic = {}
def add_num(i):
dic[get_ident()] = copy.copy(foo)
dic[get_ident()].num = i
time.sleep(1)
print(get_ident(),dic[get_ident()].num)
for i in range(5):
task = Thread(target=add_num,args=(i,))
task.start()
View Code
Flask的上下文机制就是基于Werkzeug 的 Local Stack 实现的. 而Local Stack又依赖于local类.
对于flask而言,其请求过程与django有着截然不同的流程。在django中是将请求一步步封装最终传入视图函数的参数中,但是在flask中,视图函数中并没有请求参数,而是将请求通过上下文机制完成对请求的解析操作。
上下文流程图:
上文流程:https://www.processon.com/view/link/5c963dcae4b0afc7441d6de4 密码:1230
下文流程:https://www.processon.com/view/link/5c963c48e4b02ce2e899e30b 密码:1230
源码分析
请求入口:
from flask import Flask,request
app = Flask(__name__)
app.run()
def run(self, host=None, port=None, debug=None,
load_dotenv=True, **options):
from werkzeug.serving import run_simple
try:
run_simple(host, port, self, **options)
以上app.run()的实质是执行run_simple 这个函数,下面来让我们看一下flask原生的请求,响应方式
from werkzeug.serving import run_simple
from werkzeug.wrappers import Request,Response
@request.application
def app(req):
print(req)
return Response("200 ok")
run_simple("127.0.0.1",5000,app) #此时这个app为函数名
以上,当一个请求过来的时候,函数名加()进行执行。
总结上面特点:
当我们app.run()的时候,实质也是走
run_simple(host, port, self, **options)
flask原生的请求,响应方式的也是走
run_simple("127.0.0.1",5000,app) #此时这个app为函数名
因此可以看出请求到来时,实际上是执行了对象加括号,在面向对象里面对象加括号得实质是执行了类中的app.__call__方法
所以,由以上可以看出app.run() 的本质是把app传递到 run_simple(host, port, self, **options),请求一旦进来就执行app.__call__方法
请求本质:
app.__call__,我们进入到__call__方法里面:
def __call__(self, environ, start_response): #self :app=flask(); environ:请求原始信息
return self.wsgi_app(environ, start_response)
app.py中:
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
def request_context(self, environ):
return RequestContext(self, environ)
ctx.py中:
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
以上返回request,session,app 至最初调用的ctx ,因此此时的ctx --->request,session
app.py中继续执行:
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
ctx.py中
def push(self):
top = _request_ctx_stack.top
global.py中
_request_ctx_stack = LocalStack()
local.py
def __init__(self):
self._local = Local()
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
上述可以理解为返回一个下面结构的字典:
"__local":{“__storage”:{},"__ident_func__":get_ident}
local.py中:
@property
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
上面self为"__local":{“__storage”:{},"__ident_func__":get_ident},此时这个字典类型中并没有stack,而当self._local,实质是走getattr方法:
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
因为此时self.__storage__中没有数据,所以返回一个AttributeError(name)这样的错误,至上层中
def push(self):
top = _request_ctx_stack.top
此时_request_ctx_stack 是"__local":{“__storage”:{},"__ident_func__":get_ident}
top 是:None
下面在来看
ctx.py中
_request_ctx_stack.push(self)
def push(self, obj):
rv = getattr(self._local, 'stack', None) #rv = None
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return rv
上述self为:"__local":{“__storage”:{},"__ident_func__":get_ident}
obj为:ctx
self._local.stack = rv = [] 这一步相当于执行:
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
返回一个“__storage”:{9527:{"stack":rv[]}},"__ident_func__":get_ident
rv.append(obj)
return rv
这一步相当于返回 “__storage”:{9527:{"stack":rv[ctx]}},"__ident_func__":get_ident
总结以上:上文最后返回一个属于用户的独有的request,通过local文件,把每一个request线程或者协程ID都变成了私有化,解决了数据安全的问题。
下文应用:
from flask import Flask,request
app = Flask(__name__)
if request.method == "GET":
print("ok")
app.run()
request = LocalProxy(partial(_lookup_req_object, 'request'))
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)
此时:"_local":“__storage”:{9527:{"stack":rv[ctx]}},"__ident_func__":get_ident
local.py
此时我们在去看一下top:
@property
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
因此现在的top是有数据的,所以返回一个
return getattr(top, name) 此时name= request,因此我们序列化初一个原生的request
gloables.py中:
request = LocalProxy(partial(_lookup_req_object, 'request'))
我们还需要对得到的原生request,进行本地代理(LocalProxy) 处理
def __init__(self, local, name=None):
object.__setattr__(self, '_LocalProxy__local', local)
object.__setattr__(self, '__name__', name)
if callable(local) and not hasattr(local, '__release_local__'):
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, '__wrapped__', local)
当我们用request.method时,实际上还是在调用getattr方法:
def __getattr__(self, name): name = method
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
def _get_current_object(self):
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__)
此时执行偏移函数,partial(_lookup_req_object, 'request'),返回request,并且执行method方法