Flask的上下文源码剖析
先写一段Flask程序
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.__call__ # 不加括号就不会调用,看源码方便
app.run() # 请求一旦到来,就执行app.__call__()方法
请求一旦到来,就会执行app.__call__()方法,我们先看__call__的源码。
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)
这段代码中的注释翻译过来是这样的:WSGI服务器调用Flask应用程序对象作为WSGI应用程序。这就叫: meth : ` wsgi _ app ',它可以打包应用中间件。"
这里面有envirron和start_response参数,是WSGI传过来的,传过来就处理好。然后看下一步,点击wsgi_app看源码
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) # 这里又处理一次environ
error = None
try:
try:
ctx.push() # 这里就是打包起来
response = self.full_dispatch_request() # 打包之后,要通过路由去找视图函数
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)
wsgi_app方法里面有一个ctx变量,这里面的request_context里面封装了很多方法,里面一个是处理request,一个处理session的。并且又处理了一次environ。
看request_context的源码。
def request_context(self, environ):
return RequestContext(self, environ)
它里面返回了一个RequestContext(self, enveron)对象。
看RequestContext源码
class RequestContext(object):
def __init__(self, app, environ, request=None): # 这里面的envieon是不太彻底的请求
self.app = app
if request is None: # 看着一句条件,看参数request=None,满足条件
request = app.request_class(environ) # 如果request=None就走这一行,把原生的请求给它,让他继续加工
self.request = request # 看到这就知道了前面的ctx.request就等于Request(environ)这个对象
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None # ctx.session = None
self._implicit_app_ctx_stack = []
self.preserved = False
self._preserved_exc = None
self._after_request_functions = []
self.match_request()
它里面有environ参数和request参数。看里面的if语句,如果参数request是空,就把原生的请求给request_class这个类继续加工。里面还有一个self.session,并且是空的。所以ctx.request就等于Request(environ)这个对象,ctx.session = None。并且ctx = self.request_context(environ)这一行代码不仅创建了一个对象,还实现了一个路由匹配,因为init里面还有一行代码self.match_request(),这个就是通过路由来找视图函数,这里就不多说了。综上所述request.context也就是ctx封装了处理request和session的方法。
再看wsgi_app方法里面有ctx.push(),这里就是打包。点击puth看源码
def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc) app_ctx = _app_ctx_stack.top
if 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) if hasattr(sys, 'exc_clear'):
sys.exc_clear() _request_ctx_stack.push(self) # 这就是往里放 if self.session is None: # 打包之后,这时的session还是空的,满足条件,就给session重新赋值
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)
_request_ctx_stack.push(self)这一行代码就是往里放,放的时候如果遇到多线程肯定有隔离处理,所以它里面肯定有类似threading.local()方法,local对象它自动就帮你进行数据隔离了。
点击_request_ctx_stack看源码。
_request_ctx_stack = LocalStack()
点击LocalStack看源码
class LocalStack(object):
def __init__(self):
self._local = Local()
def __release_local__(self):
self._local.__release_local__()
def _get__ident_func__(self):
return self._local.__ident_func__
def _set__ident_func__(self, value):...
def __call__(self):...
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = [] # 如果没有,就给stack创建一个空列表
rv.append(obj)
return rv
def pop(self):...
可以看到它里面又Local()对象,再看它的push方法,rv = getattr(self._local, 'stack', None)可以看到要执行这个push方法的时候要去它的self._local对象找它的stack。这就相当于,当线程进来的时候,会先拿到它的唯一标识,然后找它的stack,如果有就使用,没有就给它的stack创建一个空列表,把ctx放到列表里面,ctx里面有他自己的request和sesson,这就是打包放在这了。
# 结构就是这个形式
{
线程一的唯一标识: {stack: [ctx(request, session), ]},
线程二的唯一标识: {stack: [ctx(request, session), ]},
}
打包完之后,session还是空的,接下来就是给session重新赋值了。
_request_ctx_stack.push(self) # 这就是往里放
if self.session is None: # 打包之后,这时的session还是空的,满足条件,就给session重新赋值
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)
打包之后,session也赋值了,接下来继续看源码。
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) # 这里又处理一次environ
error = None
try:
try:
ctx.push() # 这里就是打包起来
response = self.full_dispatch_request() # 打包之后,要通过路由去找视图函数
在wsgi_app方法里面就该通过路由去找视图函数了,点击full_dispatch_request看它里面封装了什么方法。
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request() # 这个就是来执行before_request1的
if rv is None: # 如果执行之后,没有结果,就来执行我们的视图函数
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
其他的先不看,先看它的preprocess_request,看它里面封装了什么。
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, ()) # 在Flask函数执行之前,会执行一些特殊的装饰器before_request
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
所以preprocess_request就是来执行before_request的。
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request() # 这个就是来执行before_request1的
if rv is None: # 如果执行之后,没有结果,就来执行我们的视图函数
rv = self.dispatch_request() # 执行视图函数
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv) # 函数处理完,就该回去了,那么接下来做什么?看下面源码流程
如果执行之后没什么结果,就去执行我们的视图函数。
不管函数执行结果怎样,我们都会拿到一个rv。函数执行之后,会有一个返回值,那我们点击self.finalize_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
里面有一个process_response,这里面返回的是一个实例。那我们来看看这个实例里面帮我们做了什么
def process_response(self, response):
ctx = _request_ctx_stack.top
bp = ctx.request.blueprint
funcs = ctx._after_request_functions
if bp is not None and bp in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
response = handler(response)
if not self.session_interface.is_null_session(ctx.session):
self.session_interface.save_session(self, ctx.session, response)
return response
里面有after_request和save_session,原来我们已经存了用户的数据,这里是拿到数据返回给用户浏览器。
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request() # 这个就是来执行before_request1的
if rv is None: # 如果执行之后,没有结果,就来执行我们的视图函数
rv = self.dispatch_request() # 执行视图函数
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv) # 函数处理完,就该回去了,这里面做的就是执行了after、_request和save_session,把手里现有的值返回就行了
接下来我们需要往回看。
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) # 这里又处理一次environ
error = None
try:
try:
ctx.push() # 这里就是打包起来
response = self.full_dispatch_request() # 打包之后,要通过路由去找视图函数
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) # 这里面有一个ctx.auto_pop
给用户返回数据后有个finally,里面还有个ctx.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)
里面有个self.pop,继续看里面的源码。
class RequestContext(object):
def __init__(self, app, environ, request=None):......
def copy(self):......
def match_request(self):......
def push(self):...... # 刚开始传入数据就是通过push
def pop(self, exc=_sentinel):...... # 这个pop就是删掉用户请求进来的信息
就是把线程一的信息删掉,接下来执行线程二,这样用户请求进来的数据就不会混到一块了。
# 结构就是这个形式
{
线程二的唯一标识: {stack: [ctx(request, session), ]},
}
综上,可以总结为三个阶段:
第一阶段:将ctx(request/session)放到Local对象里面(Local()会给每一个线程开辟一个空间来存数据)
第二阶段:视图函数导入,处理request和session
第三阶段:请求处理完毕,获取session保存到cookie里面,然后删掉ctx
Flask的上下文源码剖析的更多相关文章
- Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)
一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...
- flask 请求上下文源码(转)
本篇阅读目录 一.flask请求上下文源码解读 二.http聊天室(单聊/群聊)- 基于gevent-websocket 回到顶部 转:https://www.cnblogs.com/li-li/p/ ...
- Flask核心机制--上下文源码剖析
一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...
- 3.flask核心与源码剖析
1.session session存储了特定用户会话所需的属性及配置信息,这样,当用户在应用程序的 Web 页之间跳转时,存储在 session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下 ...
- Flask请求上下文源码讲解,简单的群聊单聊web
请求上下文流程图 群聊html代码 <!DOCTYPE html> <html lang="en"> <head> <meta chars ...
- flask请求上下文源码分析
一.什么是上下文 每一段程序都有很多外部变量,只有像add这种简单的函数才是没有外部变量的,一旦你的一段程序有了外部变量,这段程序就不完整了,不能独立运行,你为了使他们能运行,就要给所有的外部变量一个 ...
- flask的请求上下文源码解读
一.flask请求上下文源码解读 通过上篇源码分析( ---Flask中的CBV和上下文管理--- ),我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__ ...
- Flask请求和应用上下文源码分析
flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递. flask是如何做的呢? 1:本地线程,保证即使是多个线程,自己的值也是互相隔离 1 im ...
- Flask系列10-- Flask请求上下文源码分析
总览 一.基础准备. 1. local类 对于一个类,实例化得到它的对象后,如果开启多个线程对它的属性进行操作,会发现数据时不安全的 import time from threading import ...
随机推荐
- # 20175333曹雅坤《Java程序设计》第五周学习总结
教材学习内容总结 第六章要点: 1.接口:1)接口声明: interface //接口的名字 2)接口体 2.实现接口:类实现接口:一个类需要在类声明中使用关键字implements声明该类实现一个或 ...
- go语言使用xpath
1.导包 gopm get -g -v github.com/lestrrat-go/libxml2 2.使用示例 func ExampleHTML() { res, err := http.Get( ...
- mysql GTID
之前一直通过binlog主从同步,现在发现GTID这种方式,记录一下,具体可参考网上教程.感觉配置使用更为简单方便,不知实际效果如何.
- mysql tp5 find_in_set写法
[['','exp',"FIND_IN_SET(".$data['type'].",place_category)"]]
- LeetCode 9. Palindrome Number(c语言版)
题目: Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same ...
- NOIP2018Day1T2 货币系统
题目描述 在网友的国度中共有 \(n\) 种不同面额的货币,第 \(i\) 种货币的面额为 \(a[i]\),你可以假设每一种货币都有无穷多张.为了方便,我们把货币种数为 \(n\).面额数组为 \( ...
- VisualStudio相关序列号
VisualStudio相关序列号 Visual Studio 2019 Enterprise:BF8Y8-GN2QH-T84XB-QVY3B-RC4DF Visual Studio 2019 ...
- java爬虫实现爬取百度风云榜Top10
最近在项目中遇到了java和python爬虫进行程序调用和接口对接的问题, 刚开始也是调试了好久才得出点门道. 而后,自己也发现了爬虫的好玩之处,边想着用java来写个爬虫玩玩,虽说是个不起眼的dem ...
- Python深度学习(Deep Learning with Python) 中文版+英文版+源代码
Keras作者.谷歌大脑François Chollet最新撰写的深度学习Python教程实战书籍(2017年12月出版)介绍深入学习使用Python语言和强大Keras库,详实新颖.PDF高清中文版 ...
- MiniGUI 如何显示繁体字
有两种编码格式都可以显示繁体字,一种是GBK,一种是BIG5: 由于MiniGUI官方提供的字库的字体大小太小,所以只得自己去做字库,我用一个字库生成软件,分别做了一个GBK编码的字库和一个BIG5编 ...