一起阅读flask上下文
阅读目录:
引入:
为什么设计上下文这样的机制?
就是保证多线程环境下,实现线程之间的隔离.
在了解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()
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方法