Flask之 请求,应用 上下文源码解析
什么是上下文?
每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。
这些值的集合就叫上下文。
当接受到一个请求时,我们通常将请求封装成一个HttpRequest对象,然后将对象传递给每一个视图函数,我们从request对象中获取相关的请求信息,这样做的缺点就是所有的视图函数都需要添加reqeust参数,即使视图函数中并没有使用到它。
而在Flask中,将这些信息封装成类似全局变量的东西,视图函数需要的时候,可以使用from flask import request获取。但是这些对象和全局变量不同的是:他们必须是动态的,也就是说在多线程/多协程的情况下,每个线程获取的request都是属于自己的,不会相互干扰。
引入:
对于flask而言,它的请求流程和django有着截然不同的流程。在django中,请求是一步一步封装最终传入试图函数中,但是在flask中,视图函数中并没有请求的参数,而是通过上下文的机制完成对请求的解析。
源码解析:
请求的入口
对于每次请求进来的时候,都会执行Flask的__all__方法,__call__方法执行wsgi_app方法是对请求处理以及响应的全过程,每次的上下文的创建个销毁都是在其内部实现的,上下文是flask框架的核心。 首先我们要明白,在上下文中,需要完成那些操作:
.对原生的请求进行封装,生成视图函数可以操作的request
.获取请求头中的cookie信息,生成session 对象。
.执行预处理函数和视图函数
.返回响应结果
以下为上下文源码,后续对各部分代码进行分别阐述
def wsgi_app(self, environ, start_response):
#environ 请求的原始信息,还没有经过处理
ctx = self.request_context(environ)#etc = request/session
error = None
try:
try:
ctx.push()#将ctx入栈,但是内部也将应用上下文入栈
response = self.full_dispatch_request()#对请求的url进行视图函数的匹配,执行视图函数并且返回响应信息(cookie)
except Exception as e:#如果发生错误,就将错误信息作为相应的消息进行返回
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[]
raise
return response(environ, start_response)#执行响应的消息
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)#出栈,删除本次的请求相关信息(拿空间换取时间,释放内存)
1.请求上下文对象的创建
ctx = self.request_context(environ) #拿到request和session,即把请求相关的数据都封装到了ctx这个对象中。拿到了请求的原始信息
生成了RequsetContent类实例,这个类中包含了本次请求的request和session的信息。
def request_context(self, environ):
return RequestContext(self, environ)
实例化这个类,并且将传入的原生请求信息environ封装到request类的实例红,此时,request1是封装之后的Request实例,session为None
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 self._implicit_app_ctx_stack = [] self.preserved = False self._preserved_exc = None self._after_request_functions = [] self.match_request()
request_class = Request
2.将请求上下文个应用上下文入栈
#将ctx入栈,但是内部也将应用上下文入栈
ctx.push()
def push(self): top = _request_ctx_stack.top #获取到的 top == ctx
if top is not None and top.preserved:
top.pop(top._preserved_exc)
#_app_ctx_stack和_request_ctx_stack都是Local类的实例
# 获取 应用上下文的栈顶元素,得到 app_ctx
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
# self.app == Fask()
# 得到 一个 AppContext类的实例对象,得到一个 应用上下文对象 app_ctx,此时 app_ctx拥有以下属性: app_ctx.app = app, app_ctx.g = app.app_ctx_globals_class()
app_ctx = self.app.app_context()
# 将 app_ctx 入栈,应用上下文入栈
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()
# _request_ctx_stack=LocalStack()={"_local":{"__storage__":{},"__ident__":get_ident}}
_request_ctx_stack.push(self)# self = ctx = request,session
#执行完push拿到了线程/协程的ID,拿到了name=stack,value = rv =[] # Open the session at the moment that the request context is available.
# This allows a custom open_session method to use the request context.
# Only open a new session if this is the first time the request was
# pushed, otherwise stream_with_context loses the session.
if self.session is None:
#
# SecureCookieSessionInterface()
# session_interface = SecureCookieSessionInterface(),即session_interface就是一个SecureCookieSessionInterface类的实例对象
session_interface = self.app.session_interface
# 第一次访问:生成一个 字典(容器) 返回至 self.session
self.session = session_interface.open_session(
self.app, self.request
) if self.session is None:
self.session = session_interface.make_null_session(self.app)
首先,应用上下文入栈,它的执行流程去请求上下文相同。
其次,请求上下文入栈。执行_request_ctx_stack.push(), 先看_request_ctx_stack是什么?由_request_ctx_stack = LocalStack()
由此可知,_request_ctx_stack.push()是Localstack类的实例对象。进入Localstack的构造方法中;
def __init__(self):
self._local = Local()
即在类实例化过程中,为 _request_ctx_stack 实例对象创建 _local 属性,该属性的值是
Local 类实例,进入其构造方法中,在该方法中为每一个 Local 类实例创建 __storage__ 和 __ident_func__ 属性:
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
#__slots__表示给当前对象赋值两个私有属性,有且只有两个 def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
至此,完成了对 _request_ctx_stack 实例对象创建的流程分析,但是需要注意的是,该实例对象并不是在每次请求之后才创建完成的,而是在flask项目启动之后就会被立即创建,该对象对于每次的请求都会调用该对象的push方法进行请求上下文的入栈,也就是说 _request_ctx_stack 是一个单例对象,该单例对象可以在任何的地方被调用,其他的单例对象还有:
"""
注意:
在项目启动之后,global里的代码就已经执行完毕,而且也只会执行一次,因此这里面的变量是针对所有请求所使用的,但是根据不同线程id用来存放各自的值
"""
# 生成 请求上下文栈对象,将请求上下文对象 ctx 保存到 _request_ctx_stack._local.stack = [ctx]中
_request_ctx_stack
=
LocalStack()
# 生成应用上下文栈对象,将应用上下文对象 app_ctx 保存到 _app_ctx_stack._local.stack = [app_ctx]中
_app_ctx_stack
=
LocalStack()
# current_app.__local = app
current_app
=
LocalProxy(_find_app)
# 获取ctx.request
request
=
LocalProxy(partial(_lookup_req_object,
'request'
))
# 获取 ctx.session
session
=
LocalProxy(partial(_lookup_req_object,
'session'
))
# 维护此次请求的一个全局变量,其实就是一个字典
g
=
LocalProxy(partial(_lookup_app_object,
'g'
))
对于以上的单例对象,在项目启动之后被创建,在项目停止后被销毁,与请求是否进来无任何关系。现在我们知道了 _request_ctx_stack 的创建流程,我们返回之前对请求上下文的入栈操作 _request_ctx_stack.push(self) (self指的是ctx),进入push方法:
def push(self, obj): #self = _local obj = self = ctx = request,session
"""Pushes a new item to the stack"""
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = [] #.stack相当于执__setattr__方法
rv.append(obj)
return rv
在上述流程中,首先使用反射获取 _request_ctx_stack._local.stack 的值,也就是获取请求栈的值。项目刚启动,在第一次请求进来之前,请求栈的为空,则代码继续向下执行将当前请求的ctx追加至请求栈中,并且返回请求栈的值。这里着重说一下入栈之前的流程和入栈之后的数据结构:执行 self._local.stack = rv = [] ,会调用 Local 类的 __setattr__ 方法
def __setattr__(self, name, value):#name =stack value = rv =[]
ident = self.__ident_func__() #ident 是线程协成的ID
storage = self.__storage__ #{}
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}#{ID:{"stack":[]}}
self.__ident_func__() 为获取当前此次请求的协程id或者线程id, self.__storage__ 为一个字典对象,在项目启动后的第一个请求进来之后会发生 storage[ident][name] = value 的异常错误,抛出异常被下面捕获,因此执行 storage[ident] = {name: value} (以此次协程id或线程id为key,该key的value为一个字典,在字典中存储一个键值对"stack":[ctx]),即此数据结构为:
_request_ctx_stack._local.stack={
线程id或协程id: {
'stack': [ctx]
}
}
同时, self._local.stack = [ctx]。至此,完成请求上下文的入栈操作,应用上下文与请求上下文的入栈流程相同,这里不在赘述。至此完成了请求入栈的操作,我们需要知道在上述过程中使用到的四个类: RequestContext (请求上下文类,实例对象ctx中包含了request,Session两个属性)、 Request (对请求的元信息environ进行封装)、 LocalStack (使用该类实例对象 _request_ctx_stack ,维护请求上下文对象ctx的入栈和出栈操作,相当于请求上下文对象的管理者)、 Local (堆栈类,真正存放请求上下文的类),如果你还是对着几个类关系还是不明白,请看我为你准备的图:
返回 wsgi_app 函数,继续向下执行 response = self.full_dispatch_request() 函数:
def full_dispatch_request( self ): # 将 _got_first_request = True,依次执行定义的 before_first_request 函数 self .try_trigger_before_first_request_functions() try : # 触发 request_started 信号 request_started.send( self ) # 执行钩子函数:before_request,before_first_request rv = self .preprocess_request() # 如果执行的before_request,before_first_request函数没有返回值,则继续执行视图函数。若有返回值,则不执行视图函数 if rv is None : # 执行此url对应的别名的视图函数并执行该函数,返回视图函数的返回值,得到相应信息 rv = self .dispatch_request() except Exception as e: # 如果发生错误,则将异常信息作为返回值进行返回 rv = self .handle_user_exception(e) # 封装返回信息并返回,包括 session return self .finalize_request(rv) |
在函数的内部首先执行预处理函数再执行视图函数,返回预处理函数或视图函数的返回值至浏览器。
返回 wsgi_app 函数中,继续向下执行 ctx.auto_pop(error) 函数,完成对请求上下文和应用上下文的出栈操作:
1
2
3
4
5
6
7
|
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) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
def pop( self , exc = _sentinel): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. .. versionchanged:: 0.9 Added the `exc` argument. """ app_ctx = self ._implicit_app_ctx_stack.pop() try : clear_request = False if not self ._implicit_app_ctx_stack: self .preserved = False self ._preserved_exc = None if exc is _sentinel: exc = sys.exc_info()[ 1 ] self .app.do_teardown_request(exc) # If this interpreter supports clearing the exception information # we do that now. This will only go into effect on Python 2.x, # on 3.x it disappears automatically at the end of the exception # stack. if hasattr (sys, 'exc_clear' ): sys.exc_clear() request_close = getattr ( self .request, 'close' , None ) if request_close is not None : request_close() clear_request = True finally : # 请求上下文出栈 rv = _request_ctx_stack.pop() # get rid of circular dependencies at the end of the request # so that we don't require the GC to be active. if clear_request: rv.request.environ[ 'werkzeug.request' ] = None # Get rid of the app as well if necessary. if app_ctx is not None : # 应用上下文出栈 app_ctx.pop(exc) assert rv is self , 'Popped wrong request context. ' \ '(%r instead of %r)' % (rv, self ) |
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def pop( self ): """Removes the topmost item from the stack, will return the old value or `None` if the stack was already empty. """ 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() |
stack获取到的是请求栈或应用栈的列表,栈的长度为1,则进入 elif 控制语句中,首先执行 release_local(self._local) :
1
2
3
|
def release_local(local): local.__release_local__() |
local=self._local ,即执行 Local 类的 __release_local__ 方法,进入该方法:
1
2
3
|
def __release_local__( self ): # 将 self.__storage__ 所维护的字典中删除当前协程或线程id为key的元素 self .__storage__.pop( self .__ident_func__(), None ) |
从上面的语句中可以很明显看出,要执行的操作就是将以当前协程或线程id为key的元素从字典 self.__storage__ 中删除,返回至pop函数中的elif控制语句,最终将列表中的最后一个元素返回。注意,最终 _request_ctx_stack._local 的请求栈和应用栈列表中至少会存在一个元素。
Flask的请求下文:
request = LocalProxy(partial(_lookup_req_object, 'request'))
首先执行了偏函数 _lookup_req_object,
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
@property
def top(self):
"""The topmost item on the stack. If the stack is empty,
`None` is returned.
"""
try:
return self._local.stack[-]
except (AttributeError, IndexError):
return None
执行完偏函数之后,开始执行LocalProxy这个类方法,执行完__init__方法
def __init__(self, local, name=None):#local =request偏函数
object.__setattr__(self, '_LocalProxy__local', local)
#做一个类属性_LocalProxy__local
object.__setattr__(self, '__name__', name)
if callable(local) and not hasattr(local, '__release_local__'):
#callable是否是可执行的
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, '__wrapped__', local)
然后开始执行__getattr__方法:拿到需要的
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
上下文流程图:
上文:::
下文:::
OK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Flask之 请求,应用 上下文源码解析的更多相关文章
- Flask系列10-- Flask请求上下文源码分析
总览 一.基础准备. 1. local类 对于一个类,实例化得到它的对象后,如果开启多个线程对它的属性进行操作,会发现数据时不安全的 import time from threading import ...
- Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)
一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...
- flask的请求上下文源码解读
一.flask请求上下文源码解读 通过上篇源码分析( ---Flask中的CBV和上下文管理--- ),我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__ ...
- flask 请求上下文源码(转)
本篇阅读目录 一.flask请求上下文源码解读 二.http聊天室(单聊/群聊)- 基于gevent-websocket 回到顶部 转:https://www.cnblogs.com/li-li/p/ ...
- Flask请求和应用上下文源码分析
flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递. flask是如何做的呢? 1:本地线程,保证即使是多个线程,自己的值也是互相隔离 1 im ...
- Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号
Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...
- flask源码解析之上下文
引入 对于flask而言,其请求过程与django有着截然不同的流程.在django中是将请求一步步封装最终传入视图函数的参数中,但是在flask中,视图函数中并没有请求参数,而是将请求通过上下文机制 ...
- flask源码解析之上下文为什么用栈
楔子 我在之前的文章<flask源码解析之上下文>中对flask上下文流程进行了详细的说明,但是在学习的过程中我一直在思考flask上下文中为什么要使用栈完成对请求上下文和应用上下文的入栈 ...
- [源码解析] PyTorch 分布式 Autograd (3) ---- 上下文相关
[源码解析] PyTorch 分布式 Autograd (3) ---- 上下文相关 0x00 摘要 我们已经知道 dist.autograd 如何发送和接受消息,本文再来看看如何其他支撑部分,就是如 ...
随机推荐
- 服务器端-W3Chool:服务器脚本教程
ylbtech-服务器端-W3Chool:服务器脚本教程 1.返回顶部 1. 服务器脚本教程 从左侧的菜单选择你需要的教程! SQL SQL 是用于访问和处理数据库的标准的计算机语言. 在本教程中,您 ...
- Flink架构和调度
1.Flink架构 Flink系统的架构与Spark类似,是一个基于Master-Slave风格的架构,如下图所示: Flink集群启动时,会启动一个JobManager进程.至少一个TaskMana ...
- HttpClient设置忽略SSL,实现HTTPS访问, 解决Certificates does not conform to algorithm constraints
话不多说,直接上代码. 测试API: https://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f7 ...
- Python Requests post方法中data与json参数问题
1.data参数 你想要发送一些编码为表单形式的数据——非常像一个 HTML 表单.要实现这个,只需简单地传递一个字典给 data 参数.你的数据字典在发出请求时会自动编码为表单形式,header默认 ...
- Jmeter---不同线程组的使用介绍(转)
在添加线程组:发现线程组种类挺多的 翻查资料后对几个工具进行总结: 原本想写三个 在翻阅资料,后发现下面博文比较详情, 本文大部分来自: https://blog.csdn.net/sinat_32 ...
- python调用java代码 java虚拟机(jvm)
1.新建com文件夹,在里面新建 fibnq.java package com; public class fibnq { public fibnq(){} public int fb(int n){ ...
- Go语言入门篇-基本数据类型
一.程序实体与关键字 任何Go语言源码文件都由若干个程序实体组成的.在Go语言中,变量.常量.函数.结构体和接口被统称为“程序实体”,而它们的名字被统称为“标识符”. 标识符可以是任何Unicode编 ...
- 从零构建vue项目(三)--vue常用插件
一.直接拉取的模板中,package.json如下: { "name": "vuecli2-test", "version": " ...
- 1、Java语言概述与开发环境——JDK JRE JVM理解
一.理解概念: 1.JDK(Java Development Kit Java开发工具包) JDK是提供给Java开发人员使用的,其中包含Java的开发工具,也包括JRE,所以安装了JDK,就不用单独 ...
- 搜索专题: HDU1428漫步校园
漫步校园 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...