Flask源码解析:应用启动流程

1 WSGI

所有的 python web 框架都要遵循 WSGI 协议

WSGI 中有一个非常重要的概念:每个 python web 应用都是一个可调用(callable)的对象。在 flask 中,这个对象就是 app = Flask(__name__) 创建出来的 app。要运行 web 应用,必须有 web server,比如我们熟悉的 apache、nginx ,或者 python 中的 gunicorn ,我们下面要讲到的 werkzeug 提供的 WSGIServer

Server 和 Application 之间怎么通信,就是 WSGI 的功能。它规定了 app(environ, start_response) 的接口,server 会调用 application,并传给它两个参数:environ 包含了请求的所有信息,start_response 是 application 处理完之后需要调用的函数,参数是状态码、响应头部还有错误信息。

WSGI application 非常重要的特点是:它是可以嵌套的。换句话说,我可以写个 application,它做的事情就是调用另外一个 application,然后再返回(类似一个 proxy)。一般来说,嵌套的最后一层是业务应用,中间就是 middleware。这样的好处是,可以解耦业务逻辑和其他功能,比如限流、认证、序列化等都实现成不同的中间层,不同的中间层和业务逻辑是不相关的,可以独立维护;而且用户也可以动态地组合不同的中间层来满足不同的需求。

 

2 启动流程

flask基础启动服务:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run()

这里的 app = Flask(__name__) 就是上面提到的 Application 部分,但我们并没有看到 Server 部分,那么它一定是隐藏到 app.run() 内部某个地方了。

应用启动的代码是 app.run() ,这个方法的代码如下:

def run(self, host=None, port=None, debug=None, **options):
    """Runs the application on a local development server."""
    from werkzeug.serving import run_simple

    # 如果host 和 port 没有指定,设置 host 和 port 的默认值 127.0.0.1 和 5000
    if host is None:
        host = '127.0.0.1'
    if port is None:
        server_name = self.config['SERVER_NAME']
        if server_name and ':' in server_name:
            port = int(server_name.rsplit(':', 1)[1])
        else:
            port = 5000

    # 调用 werkzeug.serving 模块的 run_simple 函数,传入收到的参数
    # 注意第三个参数传进去的是 self,也就是要执行的 web application
    try:
        run_simple(host, port, self, **options)
    finally:
        self._got_first_request = False

这个方法的内容非常简单:处理一下参数,然后调用 werkzeug 的 run_simple。需要注意的是:run_simple 的第三个参数是 self,也就是我们创建的 Flask() application。run_simple的功能就是:监听在指定的端口,收到 HTTP 请求的时候解析为 WSGI 格式,然后调用 app 去执行处理的逻辑。对应的执行逻辑在 werkzeug.serving:WSGIRequestHandler 的 run_wsgi 中有这么一段代码:

def execute(app):
    application_iter = app(environ, start_response)
    try:
        for data in application_iter:
            write(data)
        if not headers_sent:
            write(b'')
    finally:
        if hasattr(application_iter, 'close'):
            application_iter.close()
            application_iter = None

application_iter = app(environ, start_response)就是调用代码获取结果的地方

要调用 app 实例,会触发Flask类的__call__方法,代码如下:

def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
    """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app`, which can be
        wrapped to apply middleware.
        """
    return self.wsgi_app(environ, start_response)

调用app的wsgi_app方法,就是flask框架的核心代码

def wsgi_app(self, environ, start_response):
    '''
    1 request_context调用 RequestContext类,把app和environ传过去,实例化得到一个对象
    ctx就是返回的RequestContext类对象,里面包含了当前请求的request(封装后的request对象)、session以及app
    '''
    ctx = self.request_context(environ)
    error = None
    try:
        try:
            '''
            2 ctx调用RequestContext类的push方法,把ctx对象放到flask封装的local对象里的stack列表,通过线程id来区分
            生成一个session对象,把ctx对象和app_ctx对象分别放在某一个列表中	   
            '''
            ctx.push()
            #3 信号相关,请求扩展相关,路由分发-->执行视图函数,返回结果
            response = self.full_dispatch_request()
        except Exception as e:
            # 错误处理,默认是 InternalServerError 错误处理函数,客户端会看到服务器 500 异常
            error = e
            response = self.handle_exception(e)
        except:  
            error = sys.exc_info()[1]
            raise
        # 返回response对象
        return response(environ, start_response)
    finally:
        if self.should_ignore_error(error):
            error = None
        #4 最后无论成功与否,都会将ctx从local对象中移除  
        ctx.auto_pop(error)

 上面这段代码只有一个目的:找到处理函数,然后调用它。除了异常处理之外,我们还看到了 context 相关的内容(开始有 ctx.push(),最后有 ctx.auto_pop()的逻辑)。现在可以先不用管,后面会有一篇文章专门介绍。继续往后看,full_dsipatch_request 的代码如下:

def full_dispatch_request(self):
    # 3.1 执行before_first_request装饰函数,这些函数都是放在before_first_request_funcs列表中
    self.try_trigger_before_first_request_functions()
    try:
        # 发送信号
        request_started.send(self)
        # 3.2 请求扩展中的before_request装饰器装饰的视图函数(包括蓝图)返回值
        rv = self.preprocess_request()
        if rv is None:
            # 3.3 根据路由关系执行视图函数,rv是视图函数返回的值,可能是字符串,jsonfiy等
            rv = self.dispatch_request()
    except Exception as e:
        rv = self.handle_user_exception(e)
        #3.4 finalize_reqeust是对视图函数返回值进行封装成response对象
    return self.finalize_request(rv)

这段代码最核心的内容是 dispatch_request,加上请求的 hooks 处理和错误处理的内容。self.dispatch_request() 返回的是处理函数的返回结果(可能是字符串、jsonfiy等),finalize_request 会把它包装换成 Response 对象。

在 dispatch_request 之前我们看到 preprocess_request,之后看到 finalize_request,它们里面包括了请求处理之前和处理之后的很多 hooks 。这些 hooks 包括:

  • 第一次请求处理之前的 hook 函数,通过 before_first_request 定义
  • 每个请求处理之前的 hook 函数,通过 before_request 定义
  • 每个请求正常处理之后的 hook 函数,通过 after_request 定义
  • 不管请求是否异常都要执行的 teardown_request hook 函数

 try_trigger_before_first_request_functions()方法

def try_trigger_before_first_request_functions(self):
    if self._got_first_request:
        return
    with self._before_request_lock:
        if self._got_first_request:
            return
        # 装饰器before_first_request视图函数存放的列表before_first_request_funcs
        # for循环执行before_first_request装饰器下的函数
        for func in self.before_first_request_funcs:
            func()
        self._got_first_request = True

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, ())
    if bp is not None and bp in self.before_request_funcs:
        funcs = chain(funcs, self.before_request_funcs[bp])
    # funcs存放的是蓝图或者app下的before_request装饰器下的函数
    # 当其中一个函数有return返回值的时候,就不会再执行下面的函数,直接return出去了
    for func in funcs:
        rv = func()
        if rv is not None:
            return rv

finalize_request()方法

def finalize_request(self, rv, from_error_handler=False):
    # 根据视图函数返回的值生成一个响应对象
    response = self.make_response(rv)
    try:
        # process_response就是after_reqeust装饰器装饰的函数
        # 在process_response方法中,调用了save_session操作
        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

 

大致启动流程如下图:

posted @ 2022-10-09 00:02  不会钓鱼的猫  阅读(265)  评论(0编辑  收藏  举报