欢迎光临!
若无相欠,怎会相见

源码阅读 – Flask v0.1 [03] Flask 请求响应循环

序言

上篇文章写了 Flask 与 WSGI 的相关知识,详情请看 源码阅读 – Flask v0.1 [02] Flask 与 WSGI 相关知识 ,这篇文章写 Flask 请求响应循环。

Flask 中的请求响应循环

对于 Flask 的工作流程 , 最好的了解方法是从启动程序的脚本开始 , 跟着程序调用的脚步一步步深入代码的内部 。 在本节 , 我们会了解请求 – 响应循环在 Flask 中是如何处理的 : 从程序开始运行 , 第一个请求进入 , 再到返回生成的响应 。

为了方便进行单步调试 , 在这里先创建一个简单的 Flask 程序 :

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, Flask!' # 在这一行设置断点

首先在 hello 程序的 index 视图中渲染模板这一行设置断点 , 然后 PyCharm 中运行调试 。

程序启动

目前有两种方法启动开发服务器 , 一种是在命令行中使用 flask run 命令 (会调用 flask.cli.run_command() 函数) , 另一种是使用在新版本中被弃用的 flask.Flask.run() 方法 。 不论是 run_command() 函数 , 还是新版本中用于运行程序的 run() 函数 , 它们都在最后调用了 werkzeug.serving 模块中的 run_simple() 函数 , 其代码如下 :

class Flask(object):
    def run(self, host='localhost', port=5000, **options):
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)    # run_simple

[werkzeug/serving.py]

def run_simple(hostname, port, application, use_reloader=False,
            use_debugger=False, use_evalex=True,
            extra_files=None, reloader_interval=1, threaded=False,
            processes=1, request_handler=None, static_files=None,
            passthrough_errors=False, ssl_context=None):
    if use_debugger: # 判断是否使用调试器
        from werkzeug.debug import DebuggedApplication
        application = DebuggedApplication(application, use_evalex)
    if static_files:
        from werkzeug.wsgi import SharedDataMiddleware
        application = SharedDataMiddleware(application, static_files)

    def inner():
        make_server(hostname, port, application, threaded,
                    processes, request_handler,
                    passthrough_errors, ssl_context).serve_forever()

    if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
        display_hostname = hostname != '*' and hostname or 'localhost'
        if ':' in display_hostname:
            display_hostname = '[%s]' % display_hostname
        _log('info', ' * Running on %s://%s:%d/', ssl_context is None
            and 'http' or 'https', display_hostname, port)
    if use_reloader: # 判断是否使用重载器
        # Create and destroy a socket so that any exceptions are raised before
        # we spawn a separate Python interpreter and lose this ability.
        test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        test_socket.bind((hostname, port))
        test_socket.close()
        run_with_reloader(inner, extra_files, reloader_interval)
    else:
        inner()

run() 函数最后一行是 return run_simple(host, port, self, **options) , 而 run_simple() 函数的第三个参数是 application , 实际使用的时候是 self , 指的就是 Flask 对象本身 , 因此会调用当前对象的 __call__() 方法进行请求的处理 , 这时就会运行 wsgi_app()

在这里使用了两个 Werkzeug 提供的中间件 , 如果 use_debugger 为 Ture , 也就是开启调试模式 , 那么就使用 DebuggedApplication 中间件为程序添加调试功能 。 如果 static_files 为 True , 就使用 SharedDataMiddleware 中间件为程序添加提供 (serve) 静态文件的功能 。

这个方法最终会调用 inner() 函数 , 函数中的代码和之前创建的 WSGI 程序末尾很像 。 它使用 make_server() 方法创建服务器 , 然后调用 serve_forever() 方法运行服务器 。 为了避免偏离重点 , 中间在 Werkzeug 和其他模块的调用我们不再分析 。 我们在前面学习过 WSGI 的内容 , 当接收到请求时 , WSGI 服务器会调用 Web 程序中提供的可调用对象 , 这个对象就是我们的程序实例 app 。 现在 , 第一个请求进入了 。

请求 In

Flask类实现了 __call__() 方法 , 当程序实例被调用时会执行这个方法 , 而这个方法内部调用了 Flask.wsgi_app() 方法 , 如下所示 :

[flask.py]

class Flask(object):

    def wsgi_app(self, environ, start_response):
        with self.request_context(environ):
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
            response = self.make_response(rv)
            response = self.process_response(response)
            return response(environ, start_response)

    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`"""
        return self.wsgi_app(environ, start_response)

通过 wsgi_app() 方法接收的参数可以看出来 , 这个 wsgi_app() 方法就是隐藏在 Flask 中的那个 WSGI 程序 。 这里将 WSGI 程序实现在单独的方法中 , 而不是直接实现在 __call__() 方法中 , 主要是为了在方便附加中间件的同时保留对程序实例的引用 。 WSGI 程序调用了 preprocess_request() 方法对请求进行预处理 (request preprocessing) , 这会执行所有使用 before_request 钩子注册的函数 。

如果预处理没有结果 , 即为空 , 然后执行 dispatch_request , 用于请求调度 , 它会匹配并调用对应的视图函数 , 获取其返回值 , 在这里赋值给 rv 。 请求调度的具体细节我们会在后面了解 。 最后 , 接收视图函数返回值的 make_response 会使用这个值来生成响应 。 完整的调度在 wsgi_app 中已经写明了 。

响应 Out

而最终的处理也是在 wsgi_app 中 , 如下 :

def wsgi_app(self, environ, start_response):
    with self.request_context(environ):
        rv = self.preprocess_request()
        if rv is None:
            rv = self.dispatch_request()
        response = self.make_response(rv)
        response = self.process_response(response)
        return response(environ, start_response)

在函数的最后三行 , 使用 Flask 类中的 make_response() 方法生成响应对象 , 然后调用 process_response() 方法处理响应 。 返回作为响应的 response 后 , 代码执行流程就回到了 wsgi_app() 方法 , 最后返回响应对象 , WSGI 服务器接收这个响应对象 , 并把它转换成 HTTP 响应报文发送给客户端 。 就这样 , Flask 中的请求 – 循环之旅结束了 。

结语

本节就先到此结束了,后面接着写。

如有错误,敬请指出,感谢指正!    — 2021-05-10  22:27:50

赞(0) 打赏
转载请注明:飘零博客 » 源码阅读 – Flask v0.1 [03] Flask 请求响应循环
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

欢迎光临