一起阅读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()
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方法


posted @ 2019-03-23 22:05  小萍瓶盖儿  阅读(212)  评论(0编辑  收藏  举报