Flask 源码流程
Flask项目启动流程
- 实例化Flask对象
- 加载配置文件(给app的config进行赋值)
- 准备before_request、after_request、before_first_request函数
- 添加路由映射*
- 运行flask
实例化Flask对象
from flask import Flask
app = Flask(__name__,template_folder='templates',static_folder='static')
1. 对app对象封装一些初始化的值。
app.static_url_path
app.static_folder
app.template_folder
app.view_functions = {}
2. 添加静态文件的路由
self.add_url_rule(
self.static_url_path + "/<path:filename>",
endpoint="static",
host=static_host,
# 添加静态文件的路由,这个在前面url_map的那个地方见过
view_func=self.send_static_file,
)
3. 实例化了url_map的对象,以后在map对象中放 【/index/ 函数的对象应观】
class Flask(object):
url_rule_class = Rule
url_map_class = Map
def __init__(self...):
self.static_url_path
self.static_folder
self.template_folder
self.view_functions = {}
self.url_map = self.url_map_class()
app = Flask()
app.view_functions
app.url_rule_class
##### **加载配置文件(给app的config进行赋值)**
1. 读取配置文件中的所有键值对,并将键值对全都放到Config对象。(Config是一个字典)
2. 把包含所有配置文件的Config对象,赋值给 app.config
```py
from flask import Flask
app = Flask(__name__,template_folder='templates',static_folder='static',static_url_path='/xx')
# app.config = Config对象
# Config对象.from_object('xx.xx')
# 通过 app.config 拿到配置项
app.config.from_object('xx.xx')
# config.py
def from_object(self, obj):
if isinstance(obj, string_types):
obj = import_string(obj)
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
##### **准备before_request、after_request、before_first_request函数象**
```py
"""
{
None:[f1,f2,f3]
}
"""
self.before_request_funcs = {}
self.after_request_funcs = {}
self.before_first_request_funcs = []
@setupmethod
def before_request(self, f):
# 就是收集函数
self.before_request_funcs.setdefault(None, []).append(f)
return f
@setupmethod
def after_request(self, f):
# 同上
self.after_request_funcs.setdefault(None, []).append(f)
return f
@setupmethod
def before_first_request(self, f):
# 同上
self.before_first_request_funcs.append(f)
return f
##### **添加路由映射**
1. 将 url = /index 和 methods = [GET,POST] 和 endpoint = "index"封装到Rule对象
2. 将Rule对象添加到 app.url_map中。
3. 把endpoint和函数的对应关系放到 app.view_functions中。
```py
from flask import Flask
app = Flask(__name__,template_folder='templates',static_folder='static',static_url_path='/xx')
@app.route('/index')
def index():
return 'hello world'
##### **运行flask**
1. 内部调用werkzeug的run_simple,内部创建socket,监听IP和端口,等待用户请求到来。
2. 一旦有用户请求,执行app.__call__方法。
```py
class Flask(object):
def __call__(self,envion,start_response):
pass
def run(self):
run_simple(host, port, self, **options)
if __name__ == '__main__':
app.run()
```py
from flask import Flask
app = Flask(__name__,template_folder='templates',static_folder='static',static_url_path='/xx')
@app.route('/index')
def index():
return 'hello world'
if __name__ == '__main__':
app.run()
Flask 用户请求处理流程
- 创建ctx = RequestContext对象,其内部封装了 Request对象和session数据。
请求来了
# 有请求过来的话,直接调用__call__方法
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
# 核心代码
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
##### 用户请求进来发生了什么?
* environ:请求携带的信息-----> 封装成Request对象、创建session数据 ----> 绑定到一起封装成 RequestContext对象 (LocalStack)
* environ的详细值 携带的就是请求信息:
```py
{'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=1020>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x00000286D6DE1670>, 'SERVER_SOFTWARE': 'Werkzeug/0.16.1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'PATH_INFO': '/login', 'QUERY_STRING': '', 'REQUEST_URI': '/login', 'RAW_URI': '/login', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 12551, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': '127.0.0.1:5000', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_SEC_CH_UA': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', 'HTTP_SEC_CH_UA_MOBILE': '?0', 'HTTP_SEC_CH_UA_PLATFORM': '"Windows"', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', 'HTTP_SEC_PURPOSE': 'prefetch;prerender', 'HTTP_PURPOSE': 'prefetch', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'HTTP_SEC_FETCH_SITE': 'none', 'HTTP_SEC_FETCH_MODE': 'navigate', 'HTTP_SEC_FETCH_USER': '?1', 'HTTP_SEC_FETCH_DEST': 'document', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br, zstd', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'HTTP_COOKIE': 'SL_G_WPT_TO=eo; SL_GWPT_Show_Hide_tmp=1; SL_wptGlobTipTmp=1', 'werkzeug.request': <Request 'http://127.0.0.1:5000/login' [GET]>}
* 获取app对象,创建g数据 ---->绑定到一起封装成 AppContext对象 (LocalStack)
* 从RequestContext对象中的Request对象的url信息,在Map对象中找到endpoint,在view_functions中找到view
* 执行before_request函数
* 执行view函数
* 执行after_request函数
* 销毁ctx和app_ctx
##### **1. 创建ctx = RequestContext对象,其内部封装了 Request对象和session数据。**
```py
# ctx核心代码
class RequestContext:
def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
# 构建Request对象将environ进行封装
request = app.request_class(environ)
self.request = request
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
# 创建session
self.session = session</code></pre>
<pre><code class="language-py"># ctx.push方法
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不存在,创建app_ctx
app_ctx = self.app.app_context()
# 将app_ctx放入到AppLocal中
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()
# 将 ctx 放入到 RequestLocal中
_request_ctx_stack.push(self)
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()</code></pre>
<h5><strong>2. 创建app_ctx = AppContext对象,其内部封装了App和g。</strong></h5>
<p><img src="\static\img\Flask源码流程图3.png" alt="Flask源码流程图3.png" /></p>
<ul>
<li>
<p>然后ctx.push触发将 ctx 和 app_ctx 分别通过自己的LocalStack对象将其放入到Local中,Local的本质是以线程ID为key,以{“stack”:[]}为value的字典。
```py
{
1111:{“stack”:[ctx,]}
}
{
1111:{“stack”:[app_ctx,]}
}
```
</li>
</ul>
<p>注意:以后再想要获取 request/session / app / g时,都需要去local中获取。</p>
<ul>
<li>通过url_path从Map中找出Rule规则</li>
</ul>
<p><img src="\static\img\Flask源码流程图4.png" alt="Flask源码流程图4.png" /></p>
<pre><code class="language-py"># 下面代码的控制核心
def full_dispatch_request(self):
# 尝试触发before_first_request
self.try_trigger_before_first_request_functions()
try:
# 信号(钩子,可以在触发式设置操作)
request_started.send(self)
# 执行before_request
rv = self.preprocess_request()
if rv is None:
# 执行视图函数
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
# 将得到的结果序列化返回,得到Response对象
return self.finalize_request(rv)
* 为什么before_first_request只执行一次?
* 函数执行流程
* 执行所有的before_request函数
* 这个地方使用chain的原因是:蓝图中不仅有蓝图的before_request要执行,全局的before_request也要执行,所以需要将两个列表进行合二为一,得到一个迭代器对象,方便遍历执行
* 执行视图函数
* 执行所有after_request函数(session加密放到cookie中)
* response的诞生
* 销毁ctx和app_ctx
* 和带返回值的wsgi进行类比
#### **Flask session、request、app、g**
##### **偏函数**
```python
import functools
# 偏函数
def func(a1,a2):
print(a1,a2)
# 使用partial进行绑定参数形成新函数,之后的函数都可以一个参数调用
# 适合一个参数固定的那种情况
new_func = functools.partial(func,123)
new_func(2)
在flask中使用偏函数
def _lookup_req_object(name):
# ctx
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
# ctx.request
# ctx.session
return getattr(top, name)
new_func1 = functools.partial(_lookup_req_object, "request")
v = new_func1() # request
new_func2 = functools.partial(_lookup_req_object, "session")
y = new_func2() # session
# 这个类的本质就是session/request的代理(处理request和session)
class LocalProxy(object):
def __init__(self, xxx):
self.xxx = xxx() # ctx.session / ctx.reqeust
def __setitem__(self, key, value):
self.xxx[key] = value # ctx.session[key] = value
def __getitem__(self, item):
return self.xxx[item] # ctx.session[item]
def __getattr__(self, item):
return getattr(self.xxx, item) # ctx.request.method
def func():
return ctx.session
x1 = LocalProxy(func)
# 执行__setitem__
x1['k1'] = 123
# 执行__getitem__
x1['k8']
def function():
return ctx.request
x2 = LocalProxy(function)
# 执行__getattr__方法
print(x2.method)
##### **私有成员**
```python
class Foo:
def __init__(self):
self.name = 'alex'
self.__age = 123
obj = Foo()
print(obj.name)
# 不符合开发规范,但是确实可以用
print(obj._Foo__age)
alex
123
LocalProxy的源码
import functools
class LocalProxy(object):
def __init__(self, local):
object.__setattr__(self, "_LocalProxy__local", local) # self.__local = local
def _get_current_object(self):
return self.__local() # self._LocalProxy__local()
def __setitem__(self, key, value):
# ctx.session[key] = value
self._get_current_object()[key] = value
def __getattr__(self, name):
# ctx.request.method
return getattr(self._get_current_object(), name)
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
session = LocalProxy(functools.partial(_lookup_req_object, "session")) # 函数() 自动传入session
session['k1'] = 123
request = LocalProxy(functools.partial(_lookup_req_object, "request")) # 函数() 自动传入request
request.method
# session, request, current_app, g 全部都是LocalProxy对象。
"""
session['x'] = 123 ctx.session['x'] = 123
request.method ctx.request.method
current_app.config app_ctx.app.config
g.x1 app_ctx.g.x1
"""
g 是什么
在一次请求请求的周期,可以在 g 中设置值,在本次的请求周期中都可以读取或复制。相当于是一次请求周期的全局变量。
from flask import Flask,g
app = Flask(__name__,static_url_path='/xx')
@app.before_request
def f1():
g.x1 = 123
print('before', g.x1)
@app.after_request
def f1(response):
print('after', g.x1)
return response
# 只要before_request之前创建的g对象,然后after_request之后的auto_pop中销毁的,之间的所有过程中都可以使用
@app.route('/index')
def index():
print('index', g.x1)
return 'hello world'
if __name__ == '__main__':
app.run()
* Serving Flask app '__main__'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
127.0.0.1 - - [17/May/2024 14:48:57] "GET /index HTTP/1.1" 200 -
before 123
index 123
after 123
127.0.0.1 - - [17/May/2024 14:49:13] "GET /index HTTP/1.1" 200 -
before 123
index 123
after 123
总结
- 第一阶段:启动flask程序,加载特殊装饰器、路由,把他们封装 app= Flask对象中。
- 第二阶段:请求到来
- 创建上下文对象:应用上下文、请求上下文。
- 执行before / 视图 / after
- 销毁上下文对象