一、先说一下threading.local
因为flask是支持多线程的,并发就带来了对数据共享的冲突问题,threading.local的解决思路就是为每一个线程开辟空间来保存其特有的值,也就是将线程或协程的唯一标识作为key,key对应的value就是当前线程中的值,依据这个思路,flask中的Local类就是依据threading.local来实现每个线程都有自己的空间来保存值
Local类中封装了__storage__和__ident_func__属性,分别是空字典和唯一标识,用来保存线程特有的值
class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident)
二、项目启动时,globals.py文件中会实例LocalStack和LocalProxy对象
LocalStack对象会封装_local属性,就是一个Local类对象,帮我们保存线程特有值
LocalProxy对象会封装__local私有属性,是一个函数,偏函数的话就是帮我们传递参数
g是每一个请求周期都会创建的用来保存传递值的一个容器
current_app当我们打印它的时候实际上就是app对象,首先还是会调用LocalProxy的__str__方法,这个方法又会去调用偏函数执行拿到当前请求对应的AppRequest对象,返回这个对象的app属性值也就是app对象
# LocalStack对象封装了_local()属性,就是一个local对象_request_ctx_stack = LocalStack()_app_ctx_stack = LocalStack()# LocalProxy中的__local属性就是_find_app函数current_app = LocalProxy(_find_app)# LocalProxy中的__local属性就是偏函数partialrequest = LocalProxy(partial(_lookup_req_object, 'request'))session = LocalProxy(partial(_lookup_req_object, 'session'))g = LocalProxy(partial(_lookup_app_object, 'g'))
三、请求到来时
1、触发app对象的__call__方法,继而执行app对象的wsgi_app方法
2、执行app对象的request_context方法,参数environ代表请求的所有数据
ctx = self.request_context(environ)
2.1、request_context方法返回RequestContext对象,执行init方法,这个对象中会对request进行二次封装,app就是Flask的app对象,还会封装flashes和session
class RequestContext(object): 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
2.2、对request进行二次封装,app.request._class是Request类,参数environ是请求的所有数据,这个Request对象就包含了请求的所有内容
3、返回app对象的wsgi_app方法中继续执行ctx的push方法,也就是执行RequestContext的push方法
ctx.push()
3.1,在RequestContext的push方法中,首先获取_app_ctx_stack的top方法返回值,即执行LocalStack中的top方法,这个方法会去Local中查找当前线程唯一标识对应的字典中的stack的列表中的值,此时还未赋值,所以返回None,继续向下执行self.app.app_context()也就是app对象的app_context方法,这个方法返回AppContext对象,参数是app对象,执行init方法
app_ctx = _app_ctx_stack.topif app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx)else: self._implicit_app_ctx_stack.append(None)
class AppContext(object): def __init__(self, app): self.app = app self.url_adapter = app.create_url_adapter(None) self.g = app.app_ctx_globals_class()
3.2、返回的app_ctx就是AppContext对象,执行它的push方法,在这个方法中又会调用_app_ctx_stack的push方法,也就是LocalStack的push方法,参数就是app_ctx
3.3、在LocalStack的push方法中,先找到在Local中当前线程的唯一标识对应的字典,若这个字典中没有stack这个key就把一个空列表作为value值添加,然后把参数app_ctx也就是当前请求对应的AppContext对象追加到这个列表中,到这里app_ctx.push()就执行完了,注意这里的字典就是_app_ctx_stack的_local属性也就是Local对象的__storage__属性值,在这个字典中添加键值对
3.4、回到ctx的push方法中继续向下执行_request_ctx_stack的push方法,这里的self就是ctx(RequestContext对象),也就是LocalStack中的push方法,依然还是把当前请求对应的RequestContext对象保存在当前线程唯一标识对应字典中的stack列表中,注意这里的字典是_request_ctx_stack的_local属性Local对象的字典,和3.3步骤中字典是不一样的,到这里ctx的push方法就执行完了
_request_ctx_stack.push(self)
4、回到app对象的wsgi_app方法继续向下执行full_dispatch_request方法
4.1、在full_dispatch_request会先调用try_trigger_before_first_request_functions方法,方法中会会循环app对象的before_first_request_funcs属性列表,依次执行列表中的函数,也就是被@app.before_first_request装饰的函数,注意这里在锁的外部和内部都做了相同的判断,目的是为了多线程并发时能够保障被before_first_request装饰的函数只执行依次
def try_trigger_before_first_request_functions(self): """Called before each request and will ensure that it triggers the :attr:`before_first_request_funcs` and only exactly once per application instance (which means process usually). :internal: """ if self._got_first_request: return with self._before_request_lock: if self._got_first_request: return for func in self.before_first_request_funcs: func() self._got_first_request = True
4.2、回到full_dispatch_request继续向下执行preprocess_request方法,在preprocess_request方法中首先得到当前请求对应的蓝图对象会将app.before_request_funcs中key为None和蓝图对象name属性值对应的列表做chain链式操作得到一个新的列表,列表中是全局app和蓝图中被@app.before_request装饰的函数,继而循环这个新列表,依次执行每一个函数若没有得到蓝图对象,则只循环before_request_funcs中key为None的列表
def preprocess_request(self): bp = _request_ctx_stack.top.request.blueprint funcs = self.url_value_preprocessors.get(None, ()) if bp is not None and bp in self.url_value_preprocessors: funcs = chain(funcs, self.url_value_preprocessors[bp]) for func in funcs: func(request.endpoint, request.view_args) funcs = self.before_request_funcs.get(None, ()) if bp is not None and bp in self.before_request_funcs: funcs = chain(funcs, self.before_request_funcs[bp]) for func in funcs: rv = func() if rv is not None: return rv
4.3、回到full_dispatch_request继续向下执行dispatch_request方法,这个方法就是在执行相应的视图了
4.3.1、执行视图函数
4.3.2、在视图中打印request会调用LocalProxy的__str__方法,方法中会调用_get_current_object()方法,这个方法中会调用实例request对象时传递的偏函数,偏函数中会调用_request_ctx_stack的top方法,也就是LocalStack的top方法,top方法就会获取stack属性值也就是列表,取索引-1,也就是当前线程或协程对应的RequestContext对象,并且获取这个RequestContext对象的request属性值,也就是之前二次封装的Request对象,打印request就是打印Request类对象
@property def top(self): try: return self._local.stack[-1] except (AttributeError, IndexError): return None
4.3.3、在视图打印request.method会调用LocalProxy的__getsttr__方法,在LocalProxy的getattr中,还是会调用equest的_get_current_object方法得到Request对象,获取这个对象的method值
4.4、视图执行完了
4.5、回到app对象的full_dispatch_request继续向下执行finalize_request方法,参数是视图返回的响应对象,在process_response方法中还是会先得到蓝图对象,并且将app.after_request_funcs字典中key为None和蓝图对象name属性值的列表做reversed(是为了执行顺序和文件加载顺序相反),再做链式操作chain得到一个新的列表,循环这个新的列表依次执行每个函数,也就是全局app和蓝图中被@app.after_request装饰的函数并返回响应对象
def finalize_request(self, rv, from_error_handler=False): response = self.make_response(rv) try: response = self.process_response(response) request_finished.send(self, response=response) except Exception: if not from_error_handler: raise self.logger.exception('Request finalizing failed with an ' 'error while handling an error') return response
四、请求结束后
1、也就是app对象的full_dispatch_request方法执行完后没有错误,继续向下就会执行ctx的auto_pop方法,执行RequestContext的auto_pop方法,会调用ctx的pop方法,在ctx的pop方法中会调用_request_ctx_stack(localstack对象)的pop方法,在localstack的pop方法中会找到stack属性对应的列表并删掉,也就是把当前线程或协程对应的RequestContext对象ctx删掉,往后又会执行app_ctx的pop方法,也就是执行AppRequest的pop方法,在这个方法中又会执行_app_ctx_stack的pop方法也就是LocalStack的pop方法,也就是把当前请求对应的AppRequest对象pop出去