flask 源码专题(二):请求上下文与全文上下文
源码解析
0. 请求入口
if __name__ == '__main__':
app.run()
def run(self, host=None, port=None, debug=None,
load_dotenv=True, **options):
# Change this into a no-op if the server is invoked from the
# command line. Have a look at cli.py for more information.
................
from werkzeug.serving import run_simple try:
run_simple(host, port, self, **options)
finally:
# reset the first request information if the development server
# reset normally. This makes it possible to restart the server
# without reloader and that stuff from an interactive shell.
self._got_first_request = False
def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)
对于每次请求进来之后,都会执行Flask类实例的__call__方法,至于为什么执行的是__call__方法请看本博主的flask 源码专题(一):app.run()的背后 但是我们可以知道的是,只要是请求进来之后,__call__方法执行的 wsgi_app方法是对请求处理及响应的全部过程,对于每次请求上下文的创建和销毁也是在其内部完成,而上下文正是flask框架的核心,因此研究其内部的执行流程有着至关重要的作用。
首先我们要明白,在上下文中,要完成的操作是:
1.对原生请求进行封装,生成视图函数可操作的request对象
2.获取请求中的cookie信息,生成Session对象
3.执行预处理函数和视图函数
4.返回响应结果
以下为上下文源码,后续对各部分代码进行分别阐述
def wsgi_app(self, environ, start_response):
# 生成 ctx.request , request.session,请求上下文,即请求先关的数据都封装到了 ctx这个对象中去
ctx = self.request_context(environ)#1.请求上下文对象的创建
error = None
try:
try:
# 将ctx入栈,但是内部也将应用上下文入栈
ctx.push()#2. 将请求上下文和应用上下文入栈
# 对请求的url进行视图函数的匹配,执行视图函数,返回响应信息(cookie)
response = self.full_dispatch_request()#3
except Exception as e:
# 若发生错误将错误信息最为相应信息进行返回
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
# 封装响应信息
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 出栈,删除本次请求的相关信息
ctx.auto_pop(error)#4 完成对请求上下文和应用上下文的出栈操作
1.请求上下文对象的创建
# 生成 ctx.request , request.session,请求上下文,即请求先关的数据都封装到了 ctx这个对象中去
ctx = self.request_context(environ)
生成RequestContext类实例,该实例包含了本次请求的request和Session信息
def request_context(self, environ):
return RequestContext(self, environ)
对生成的类实例进行初始化,并且将传入的原生请求信息environ封装值Request类实例中。此时,request为封装之后的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._after_request_functions = [] self.match_request()
request_class = Request
2. 将请求上下文和应用上下文入栈
# 将ctx入栈,但是内部也将应用上下文入栈
ctx.push()
def push(self):
# 获取到的 top == ctx
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc) # Before we push the request context we have to ensure that there
# is an application context.
"""
_request_ctx_stack 和 _app_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() # self 指的是 ctx,即将ctx入栈,即 _request_ctx_stack._local.stack = [ctx]。请求上下文入栈
_request_ctx_stack.push(self)
# 由于每次请求都会初始化创建你ctx,因此session都为None
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)
上下文入栈与出栈其他详细的说明请参考flask 源码专题(三)
将ctx入栈,主要完成有
2.1.把app_ctx上下文对象添加到了_app_ctx_stack这个栈中
2.2.把 ctx请求对象添加到Local类的列表中
2.3.执行open_session方法,把session加载到内
3返回 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)
在函数的内部首先执行预处理函数再执行视图函数,返回预处理函数或视图函数的返回值至浏览器。
4 返回 wsgi_app 函数中,继续向下执行 ctx.auto_pop(error) 函数,完成对请求上下文和应用上下文的出栈操作:
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)
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)
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) :
def release_local(local): local.__release_local__()
local=self._local ,即执行 Local 类的 __release_local__ 方法,进入该方法:
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 源码专题(二):请求上下文与全文上下文的更多相关文章
- flask 源码专题(五):SqlAlchemy 中操作数据库时session和scoped_session的区别
1原生session: from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from sqlalc ...
- flask 源码专题(十一):LocalStack和Local对象实现栈的管理
目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于localstack的实现 3. 总结 04 LocalS ...
- flask 源码专题(九):flask扩展点
1. 信号(源码) 信号,是在flask框架中为我们预留的钩子,让我们可以进行一些自定义操作. pip3 install blinker 2. 根据flask项目的请求流程来进行设置扩展点 中间件 # ...
- Flask源码分析二:路由内部实现原理
前言 Flask是目前为止我最喜欢的一个Python Web框架了,为了更好的掌握其内部实现机制,这两天准备学习下Flask的源码,将由浅入深跟大家分享下,其中Flask版本为1.1.1. 上次了解了 ...
- flask 源码专题(三):请求上下文和应用上下文入栈与出栈
1.请求上下文和应用上下文入栈 # 将ctx入栈,但是内部也将应用上下文入栈 ctx.push() def push(self): # 获取到的 top == ctx top = _request_c ...
- Python Web Flask源码解读(二)——路由原理
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- flask 源码专题(六):session处理机制
前言 flask_session是flask框架实现session功能的一个插件,用来替代flask自带的session实现机制,flask默认的session信息保存在cookie中,不够安全和灵活 ...
- flask 源码专题(十):flash源码研究
flash源码 def flash(message, category="message"): flashes = session.get("_flashes" ...
- flask 源码专题(八):路由加载
1.示例代码 from flask import Flask app = Flask(__name__,static_url_path='/xx') @app.route('/index') def ...
随机推荐
- Centos7 systemctl添加自定义系统开机服务
Centos7的服务systemctl脚本存放在: /usr/lib/systemd/ 有系统(system)和用户(user)之分,需要开机不登陆就能运行的程序,存下系统服务里,即:/usr/lib ...
- Scrum Master教你四招,瓦解团队内部刺头
摘要:<Scrum精髓>一书中将Scrum Master的职责总结为六类:敏捷教练,服务型领导,“保护伞”,“清道夫”,过程权威,“变革代言人”.作为“保护伞“,Scrum Master应 ...
- cb41a_c++_STL_算法_填充新值fill_generate
cb41a_c++_STL_算法_填充新值fill_generatefill(b,e,v)fill_n(b,n,v),填充n个vgenerate(b,e,p)generate_n(b,n,p) gen ...
- cb29a_c++_STL_算法_查找算法_(2)search_n
cb29a_c++_STL_算法_查找算法_(2)search_n//比如:连续查找连续的n个8search_n(b,e,c,v),迭代器b,begin(),e,end().连续的c个vpos=sea ...
- ca78a_c++_字符串流在内存中的输入输出(速度快)
/*ca78a_c++_字符串流在内存中的输入输出**字符串流:在内存中的输入输出.(在内存中进行,速度快)**文件流 :是对文件进行输入和输出.(在磁盘里面进行)istringstream(输入), ...
- c++. Run-Time Check Failure #2 - Stack around the variable 'cc' was corrupted.
Run-Time Check Failure #2 - Stack around the variable 'cc' was corrupted. char cc[1024]; //此处如果索引值 ...
- Git 居然可以用来跟女神聊天?
Git 是用来做啥的?想必码农朋友都知道,Git 是版本控制软件,是软件开发过程中团队协作不可或缺的软件. 但是,作为版本控制软件的 Git ,能跟聊天工具扯上关系吗?这二者似乎毫无关系,但脑洞大开的 ...
- 代码规范与计划(Beta阶段)
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 团队名称 WeChair 这个作业要求在哪里 Beta冲刺 这个作业的目标 代码规范与计划 作业正文 如下 其他参考文献 代码规 ...
- ODBC 常见数据源配置整理
目录 1. 简介 1.1 ODBC和JDBC 1.2 ODBC配置工具 1.3 ODBC 数据源连接配置 2. MySQL 数据源配置 2.1 配置步骤 2.2 链接参数配置 3. SQLServer ...
- Maven的pom文件依赖提示 ojdbc6 Missing artifact,需要手动下载并导入maven参考
eg: 需要 ojdbc6.jar 的下载地址 https://www.oracle.com/database/technologies/jdbcdriver-ucp-downloads.html c ...