4. Flask 源码流程

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
    • 销毁上下文对象