flask源码分析(一)

438 阅读4分钟

前言

本系列将对python几个流行的web框架源码进行分析。主要分析各个框架的特点、特色功能实现原理、以及一个请求在框架中的经过哪些处理。 文章不打算精读每一行代码,而是挑出框架比较有特色的地方深入研究,对于其他内容,采取粗读的形式。 预计会选取flask,fastapi,Django这三个框架。 每个框架各有特色,所以文章没有固定格式和必要篇目。

工具准备

python版本:3.10
flask源码版本:3.0.X
flask源码地址:github.com/pallets/fla…

flask简介

flask,作为python web开发入门的必学框架,以其简洁明了的特点成为许多开发者的首选。通过flask,不仅可以轻松快速地构建一个接口,而且其强大的扩展性使得开发者能够根据项目需求自由定制功能。flask社区的活跃度也是其引人注目的一点,不仅体现在丰富的插件生态上,还表现在用户之间的积极合作和问题解决的高效率。目前,flask在GitHub上拥有超过65.2k的星标,这不仅仅是数字,更是对其卓越质量和受欢迎程度的有力证明。

第一印象

按照惯例,用flask写一个hello world,看看它的第一感觉。

# 导入 Flask 类
from flask import Flask

# 创建一个 Flask 应用实例
app = Flask(__name__)

# 定义路由,当访问根路径时返回 "Hello, World!"
@app.route('/')
def hello_world():
    return 'Hello, World!'

# 运行应用
if __name__ == '__main__':
    app.run(debug=True)

可以看到上面的代码,非常简洁,一目了然。
用Flask类创建一个实例,__name__ 参数表示当前模块的名称。
用app.route绑定路由
hello_world()就是这个请求处理的业务逻辑。
最后用flask自带的服务器启动。

werkzeug

在开始进行flask的源码分析之前,我们得先分析一下werkzeug。werkzeug是flask的基础,提供了诸多功能,想要了解flask的工作原理,花费一定篇幅介绍werkzeug是很有必要的。

run函数

先来看看app.run是如何启动一个服务的。查看源码,run实际上调用的是:

# .../flask/app.py文件
def run(  
    self,  
    host: str | None = None,  
    port: int | None = None,  
    debug: bool | None = None,  
    load_dotenv: bool = True,  
    **options: t.Any,  
    ) -> None:
    
    .....
    
    try:  
        run_simple(t.cast(str, host), port, self, **options)  
    finally:   
        self._got_first_request = False

这里的run_simple就是werkzeug提供的:

def run_simple(  
    hostname: str,  
    port: int,  
    application: WSGIApplication,  
    use_reloader: bool = False,  
    use_debugger: bool = False,  
    use_evalex: bool = True,  
    extra_files: t.Iterable[str] | None = None,  
    exclude_patterns: t.Iterable[str] | None = None,  
    reloader_interval: int = 1,  
    reloader_type: str = "auto",  
    threaded: bool = False,  
    processes: int = 1,  
    request_handler: type[WSGIRequestHandler] | None = None,  
    static_files: dict[str, str | tuple[str, str]] | None = None,  
    passthrough_errors: bool = False,  
    ssl_context: _TSSLContextArg | None = None,  
    ) -> None:
    
    ...
    
    

flask将启动参数传递给werkzeug。
run_simple接收host、port等参数。 最重要的是application这个参数,它要求的是一个WSGIApplication

WSGIApplication = Callable[[WSGIEnvironment, StartResponse], Iterable[bytes]] 

可以看到,WSGIApplication定义是一个【可调用的】、接收两个参数、返回一个可迭代对象的对象。也就是需要符合WSIG要求的,它可以是一个函数,也可以是一个内部实现了__call__的对象。

flask调用run_simple(t.cast(str, host), port, self, **options),把本身传递进去。所以我们看看flask的WSGI实现:

# __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)

# wsgi_app
def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
    ctx = self.request_context(environ)
    error: BaseException | None = 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 "werkzeug.debug.preserve_context" in environ:
            environ["werkzeug.debug.preserve_context"](_cv_app.get())
            environ["werkzeug.debug.preserve_context"](_cv_request.get())

        if error is not None and self.should_ignore_error(error):
            error = None

        ctx.pop(error)

这段代码没什么可说的,就是按照WSGI的要求,接收特定的参数和返回特定的值。 值得关注的是内部的requestresponse以及请求上下文ctx,他们将会实际处理请求。后面将解析flask的请求对象以及响应对象。

总结

flask依赖werkzeug进行封装,上述内容只是简单介绍werkzeug的run_simple,太过详细介绍werkzeug将会偏离主题,后续的分析中还会多次遇到werkzeug的源码,到时候再进行逐步分析。

flask是实现WSGI协议的框架,因此,处理请求的函数或者类(就是上面的wsgi_app)一定需要按照WSGI的接口要求,在此基础上再开展请求处理、响应处理、错误捕捉之类的内容。
知道了每次请求将会调用wsgi_app之后,我们就可以从wsgi_app内部开始分析。