当前位置: 首页 > 后端技术 > Python

Flask源码分析(三)Flask的请求上下文

时间:2023-03-26 16:06:56 Python

flask是如何管理上下文的1.什么是上下文?通俗的说就是一个容器,里面存放了一些与程序运行相关的参数变量等,这些内容支撑着程序。跑步。Flask中讨论了两种上下文:请求上下文和应用程序上下文。比如常用的g、session、request就属于请求上下文,它们的内容只在各自的请求中有效。而current_app是应用上下文。Flask引入了应用上下文的概念,以更好地支持多应用开发。2、flask如何管理上下文2.1接上一篇falsk如何处理请求的文章。上一篇文章讲wsgi_app的时候提到过调用self.request_context方法时会创建requestcontext对象。defwsgi_app(self,environ,start_response):#创建当前请求的上下文空间ctx=self.request_context(environ)error=Nonetry:try:#将上下文压入堆栈ctx.push()response=self.full_dispatch_request()除了Exceptionase:error=eresponse=self.handle_exception(e)except:#noqa:B001error=sys.exc_info()[1]raisereturnresponse(environ,start_response)finally:ifself.should_ignore_error(error):error=None#从栈空间中弹出当前上下文ctx.auto_pop(error)2.2我们来看看self.request_context执行了什么(部分源码已删)。在内部,首先实例化了RequestContext类(名字很明显),实例化方法还实例化了request_class类(继承自BaseRequest),返回request对象,其中包含了请求的相关信息。RequestContext类中实现了两个重要的方法:push和pop方法。看到这里,我隐约明白了请求上下文的处理流程。上面提到的wsgi_app中的full_dispatch_request方法在处理一个请求的时候,会去_request_ctx_stack获取栈顶的请求上下文(可以继续看源码,内容太多,就不展开了posted),请求处理完成后,返回相应的response对象,然后调用auto_pop(内部调用pop)从栈空间弹出请求上下文。Flask支持多线程和协程。比如多线程访问时,flask如何保证不会从同一个获取请求上下文?defrequest_context(self,environ):#self为当前app,environ为请求参数returnRequestContext(self,environ)classRequestContext(object):def__init__(self,app,environ,request=None,session=None):self.app=appifrequestisNone:request=app.request_class(environ)self.request=requestself._implicit_app_ctx_stack=[]self.preserved=Falsedefpush(self):"""将请求上下文绑定到currentcontext."""#获取_request_ctx_stack栈顶的请求上下文top=_request_ctx_stack.top#如果有异常,会导致最后一个请求上下文不能正常弹出,这里保证没有如果top不是None和top,则在堆栈顶部请求上下文。preserved:top.pop(top._preserved_exc)#获取_app_ctx_stack栈顶的应用上下文app_ctx=_app_ctx_stack.top#如果app_ctx为None或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)ifhasattr(sys,"exc_clear"):sys.exc_clear()#将自动压入_request_ctx_stack栈中_request_ctx_stack.push(self))defpop(self,exc=_sentinel):app_ctx=self._implicit_app_ctx_stack.pop()try:clear_request=Falseifnotself._implicit_app_ctx_stack:self.preserved=Falseself._preserved_exc=Noneifexcis_sentinel:exc=sys.exc_info()[1]self.app.do_teardown_request(exc)如果hasattr(sys,"exc_clear"):sys.exc_clear()request_close=getattr(self.request,"close",None)如果request_close不是None:request_close()clear_request=Truefinally:#弹出当前请求上下文rv=_request_ctx_stack.pop()ifclear_request:rv.request.environ["werkzeug.request"]=None#如有必要,也删除应用程序。ifapp_ctxisnotNone:app_ctx.pop(exc)#Makesuretopop上下文空间本身assertrvisself,"Poppedwrongrequestcontext.(%rinsteadof%r)"%(rv,self,)2.3答案是使用线程或协程的唯一标识,即函数get_ident_request_ctx_stack源码,_request_ctx_stack是一个全局变量。Flask类首次实例化时,会实例化LocalStack类,并导入此变量。当调用push方法时,会触发self._local的__getattr__方法。如果self._local没有存储当前线程或协程的唯一标识,则会触发自己的__setattr__方法,然后将当前请求上下文存储在这个__storage__属性中,以保证在调用时正确使用对应的上下文提出并发请求。_request_ctx_stack=LocalStack()_app_ctx_stack=LocalStack()try:fromgreenletimportgetcurrentasget_identexceptImportError:try:fromthreadimportget_identexceptImportError:from_threadimportget_identclassLocal(object):__slots__=("__storage__","__ident_func__")定义__init__(self):object.__setattr__(self,"__storage__",{})object.__setattr__(self,"__ident_func__",get_ident)def__getattr__(self,name):尝试:返回self.__storage__[self.__ident_func__()][name]除了KeyError:raiseAttributeError(name)def__setattr__(self,name,value):ident=self.__ident_func__()storage=self.__storage__try:storage[ident][name]=value除了KeyError:storage[ident]={name:value}classLocalStack(object):def__init__(self):self._local=Local()defpush(self,obj):"""Pushesanewitemtothestack"""#Callthe__getattr__methodintheself._localobjectrv=getattr(self._local,"stack",None)ifrvisNone:#调用self._local对象方法中的__setattr__方法设置当前线程或协程的唯一标识self._local.stack=rv=[]#将当前请求上下文压入栈中#self._local中__storage__的最终内容类似于:#{241253254325:{'stack':RequestContext}}rv.append(obj)returnrvdefpop(self):pass@propertydeftop(self):pass