# 请求上下文源码分析
# 上下文涉及到三个类,我们以reqeust为例
首先我们以一个简单的实例为切入点
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'hello world' if __name__ == "__main__": app.run()
# Flask的全局Local,该文件在程序启动时就加载了
# context locals,位于flask.globals文件中 _request_ctx_stack = LocalStack() #加载全局请求数据 _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) #全局变量,request导入即可使用,去local中获取ctx,然后去ctx中获取request request = LocalProxy(partial(_lookup_req_object, "request")) #加载session,方法和上面一致,去local中获取ctx,然后去ctx中获取session,和request机制一样 session = LocalProxy(partial(_lookup_req_object, "session")) g = LocalProxy(partial(_lookup_app_object, "g")) #偏函数使用来指定了,专用来去ctx的request,参数已经给了_lookup_req_object,只需要再次调用偏函数时,就会带有request参数
# 启动
1、app.run() # 执行run方法,加载的时候程序就执行run函数,触发下面的函数 2、run_simple(host, port, self, **options)函数,从而触发app的__call__方法 3、在__call__函数中调用self.wsgi_app函数,传入的environ是请求的最原始数据,start_response是发送响应的函数 return self.wsgi_app(environ, start_response) 4、重点,Flask的所有请求都是通过下面这是几行代码过去的:(可在此定制中间件,在Flask类中) def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) #将请求相关数据environ封装到RequestContext中 error = None try: try: ctx.push() #讲ctx对象封装到local中(每个线程/协程中独立的空间) response = self.full_dispatch_request() # 处理所有视图函数,返回给response,如果你们发生报错就会被下面的异常捕捉 except Exception as e: #捕捉异常 error = e response = self.handle_exception(e) #处理异常,返回给response except: # noqa: B001 error = sys.exc_info()[1] raise return response(environ, start_response) finally: #finally表示最后都要进行处理,即将该协程创建的协程给销毁 if self.should_ignore_error(error): error = None ctx.auto_pop(error) #最后自动将自己的数据从__storage__中进行删除
# wsgi_app函数的代码分析
ctx = self.request_context(environ)
将请求相关数据environ封装到RequestContext对象中,返回给ctx,其中ctx中包含了flashes、sesson、request等1、函数中传入了environ,即所有的请求原始数据,传入了request_context函数中 2、点击request_context函数: #其函数只有下面一句,取实例化一个类并返回实例对象 return RequestContext(self, environ) 3、进入RequestContext类,执行__init__函数: def __init__(self, app, environ, request=None, session=None): self.app = app #当前app名称 if request is None: #默认request必然是None,只传了一个environ request = app.request_class(environ) #会app中找request_class,即回到了Flask类中,找到了:request_class = Request,调用Request类,传入environ参数,最后返回经过封装的request,我们可以调用其中的方法了 self.request = request #也就是说ctx可以ctx.app、ctx.request、ctx.session等属性 self.url_adapter = None try: self.url_adapter = app.create_url_adapter(self.request) except HTTPException as e: self.request.routing_exception = e self.flashes = None #闪现 self.session = session self._implicit_app_ctx_stack = [] self.preserved = False self._preserved_exc = None self._after_request_functions = []
ctx.push()
:将封装的请求相关数据的RequestContext对象中的对象,添加到_requset_ctx_stack.push(self)
中1、通过上面返回的ctx,里面除了封装了一些属性外,还封装了方法,比如push 2、在RequestContext类中找到push函数: def push(self) #self就是我们传入的ctx,也就是RequestContext对象 top = _request_ctx_stack.top #top被封装的属性,通过触发__getattr__获取有没有数据 if top is not None and top.preserved: #preserved本身为False,用于指示是否保留,之前的数据,如果设置了True,则不会保存之前出错的 top.pop(top._preserved_exc) #用于记录报错异常,出错的会被pop掉 app_ctx = _app_ctx_stack.top #这个和request_ctx_stack类似,, if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() #如果又=有app,则执行app_context,里面实例化AppContext,返回一个对象 app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) if hasattr(sys, "exc_clear"): sys.exc_clear() #self是requestContext对象,其中包含请求相关的所有数据 _request_ctx_stack.push(self) # 通过分析_request_ctx_stack是LocalStack()的对象,所以应该取LocalStack中找push函数,将app也加入local中的storage中,数据 if self.session is None: session_interface = self.app.session_interface self.session = session_interface.open_session(self.app,self.request) if self.session is None: self.session =session_interface.make_null_session(self.app) if self.url_adapter is not None: self.match_request() 2.2:触发的top属性: @property def top(self): """The topmost item on the stack. If the stack is empty,`None` is returned. """ try: return self._local.stack[-1] #. 点触犯__getattr__方法传入stack,取出对象的列表,取-1 except (AttributeError, IndexError): #发生异常则return None return None 3、我们之前说了,在项目启动的时候,我们就运行了_request_ctx_stack = LocalStack(),执行LocalStack的init函数: def __init__(self): self._local = Local() # 这个Local就是我们上一章手写的那个Local类似的类 4、点击Local类: class Local(object): __slots__ = ("__storage__", "__ident_func__") def __init__(self): object.__setattr__(self, "__storage__", {}) #存储所有协程id,以及请求数据,只能这么设置否则迭代过多 object.__setattr__(self, "__ident_func__", get_ident) #获取id ... 5、LocalStack中找push函数: def push(self, obj): #注意这里的obj,就是传入的self,即ctx(封装了所有封装好的数据和方法) """Pushes a new item to the stack""" rv = getattr(self._local, "stack", None) #_local就是Local类的对象, if rv is None: # 最开始是空的 self._local.stack = rv = [] # .点会触发的是Local的__setattr__函数,赋值给__storage__ rv.append(obj) return rv 6、触发的__setattr__函数: def __setattr__(self, name, value): #name = stack, value = [] ident = self.__ident_func__() # 查找id storage = self.__storage__ try: storage[ident][name] = value #添加值 except KeyError: storage[ident] = {name: value} 7、大致的结构: { 唯一标识:{ 'stack':[ctx,] } }
response = self.full_dispatch_request()
处理所有的视图函数,并获得响应给response
1、点击进full_dispatch_request()函数: def full_dispatch_request(self): self.try_trigger_before_first_request_functions() #请求进来先处理before_first_request try: request_started.send(self) rv = self.preprocess_request() if rv is None: rv = self.dispatch_request() except Exception as e: rv = self.handle_user_exception(e) return self.finalize_request(rv) 2、点击try_trigger_before_first_request_functions函数: def try_trigger_before_first_request_functions(self): if self._got_first_request: #默认配置的_got_first_request=False return with self._before_request_lock: #_before_request_lock=Lock()是把锁 if self._got_first_request:return for func in self.before_first_request_funcs: #循环first列表,有就会添加列表中 func() self._got_first_request = True 3、点击preprocess_request函数: def preprocess_request(self): bp = _request_ctx_stack.top.request.blueprint # 去获得蓝图的名字,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 3.2、点击blueprint函数: @property def blueprint(self):#在这里的request if self.url_rule and "." in self.url_rule.endpoint: return self.url_rule.endpoint.rsplit(".", 1)[0] #切割
ctx.auto_pop(error)
调用RequestContext类中的auto_pop函数,error默认为None1、点击auto_pop函数,查看源码 def auto_pop(self, exc): if self.request.environ.get("flask._preserve_context") or ( exc is not None and self.app.preserve_context_on_exception ): self.preserved = True self._preserved_exc = exc else: self.pop(exc) #又执行了本类的pop函数,exc为当前传过来的error 2、点击self.pop,调用_request_ctx_stack进行删除相应的额值,又调回LocalStack类中的pop函数 rv = _request_ctx_stack.pop() 3、LocalStack类中的pop函数 def pop(self): stack = getattr(self._local, "stack", None) if stack is None: return None elif len(stack) == 1: release_local(self._local) return stack[-1] else: return stack.pop()
# 全局request
上面我们已经对每个请求的数据进行了封装,那么全局中的视图是怎么获取到request?
视图中要使用request,必须导入
from flask import request
,点击查看源码1、在全局加载部分,requeset是LocalProxy的对象 request = LocalProxy(partial(_lookup_req_object, "request")) #在全局配置中获取的request,偏函数已经给_lookup_req_object传递了request参数
点击LocalProxy类,查看源码
class LocalProxy(object): __slots__ = ("__local", "__dict__", "__name__", "__wrapped__") def __init__(self, local, name=None): #local是传入的函数_lookup_req_object object.__setattr__(self, "_LocalProxy__local", local) #通过_类名__属性名创建一个方法 object.__setattr__(self, "__name__", name) if callable(local) and not hasattr(local, "__release_local__"): object.__setattr__(self, "__wrapped__", local)
在LocalProxy类中有很多方法,而我们已知在视图函数中使用request属性,下面我 们以
print(request)
为例1、print(request) #其实触发的时LocalProxy类中的__str__方法: print(request.method) #会触发__getattr__方法,并将method传入 __str__ = lambda x: str(x._get_current_object()) 2、点击查看_get_current_object函数 def _get_current_object(self): if not hasattr(self.__local, "__release_local__"): #最初这里是没有的所有能通过 return self.__local() # 执行_lookup_req_object() try: return getattr(self.__local, self.__name__) #去__local中获取 except AttributeError: raise RuntimeError("no object bound to %s" % self.__name__) #__name__在init函数中已经设置为了“request”,也就是说获取全局变量中找到当前request对象
self.__local
会触发local函数,即_lookup_req_object
函数def _lookup_req_object(name): top = _request_ctx_stack.top #取出stack[-1]对应的值 if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) #top是返回的数据RequestContext对象中去获取request属性,也就是Request对象, @property def top(self): try: return self._local.stack[-1] # 返回stack[ctx,]
# 总结
1、threading.Local 和 Flask 自定义Local对象
2、请求到来到来时
2.1 ctx = 封装RequestContext(request,session)
2.2 ctx 放到Local中
3、执行视图函数
3.1 self.full_dispatch_request()函数中进行处理函数
3.2 如何调用全局request?
在程序启动时就加载好了全局request属性、session属性等,request是LocalProxy类的对象,调用request的属性或方法时会触发__str__、__getattr__、__add__等一堆方法,
3.3 所有的方法都会调用偏函数,_lookup_req_object 来从全局的local中获取request对象,-1是去列表的唯一值
4、finally中执行ctx.atuo_pop(),删除每次进来的request
# 模拟取属性的过程
from flask.globals import _request_ctx_stack
from functools import partial
def _lookup_req_object(name):
# name = request
# top = ctx
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('不存在')
return getattr(top, name) # 获取响应的属性
class Foo(object):
def __init__(self):
self.xxx = 123
self.ooo = 88
req = partial(_lookup_req_object,'xxx')
xxx = partial(_lookup_req_object,'ooo') #偏函数
_request_ctx_stack.push(Foo()) #将类添加进去
v = req() #这时候才去取
print(v)
v1=xxx()
print(v1)
_request_ctx_stack.pop() #请求终止,删除响应数据