flask应用启动流程

WSGI

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

在这里还是要简单回顾一下 WSGI 的核心概念。

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

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

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

WSGI 的内容就讲这么多,我们来看看 flask 的 hello world 应用:

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

NOTE:为了阅读方便,我删除了注释和不相干的部分,下面所有的代码都会做类似的处理,不再赘述。

这个方法的内容非常简单:处理一下参数,然后调用 werkzeugrun_simple。需要注意的是:run_simple 的第三个参数是 self,也就是我们创建的 Flask() application。因为 WSGI server 不是文章的重点,所以我们就不深入讲解了。现在只需要知道它的功能就行:监听在指定的端口,收到 HTTP 请求的时候解析为 WSGI 格式,然后调用 app 去执行处理的逻辑。对应的执行逻辑在 werkzeug.serving:WSGIRequestHandlerrun_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 实例,那么它就需要定义了 __call__ 方法,我们找到 flask.app:Flask 对应的内容:

def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response) def wsgi_app(self, environ, start_response):
"""The actual WSGI application.
"""
# 创建请求上下文,并把它压栈。这个在后面会详细解释
ctx = self.request_context(environ)
ctx.push()
error = None try:
try:
# 正确的请求处理路径,会通过路由找到对应的处理函数
response = self.full_dispatch_request()
except Exception as e:
# 错误处理,默认是 InternalServerError 错误处理函数,客户端会看到服务器 500 异常
error = e
response = self.handle_exception(e)
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 不管处理是否发生异常,都需要把栈中的请求 pop 出来
ctx.auto_pop(error)

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

继续往后看,full_dsipatch_request 的代码如下:

def full_dispatch_request(self):
"""Dispatches the request and on top of that performs request
pre and postprocessing as well as HTTP exception catching and
error handling.
"""
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)

这段代码最核心的内容是 dispatch_request,加上请求的 hooks 处理和错误处理的内容。

NOTE:self.dispatch_request() 返回的是处理函数的返回结果(比如 hello world 例子中返回的字符串),finalize_request 会把它转换成 Response 对象。

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

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

dispatch_request 要做的就是找到我们的处理函数,并返回调用的结果,也就是路由的过程

Flask应用启动流程的更多相关文章

  1. Flask依赖和启动流程回顾

    flask 有两个核心依赖库:werkzeug 和 jinja,而 werkzeug 又是两者中更核心的. werkzeug werkzeug负责核心的逻辑模块,比如路由.请求和应答的封装.WSGI ...

  2. Python Web Flask源码解读(一)——启动流程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  3. Flask源码解析:Flask应用执行流程及原理

    WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...

  4. Flask源码流程分析(一)

    Flask源码流程分析: 1.项目启动: 1.实例化Flask对象 1. 重要的加载项: * url_rule_class = Rule * url_map_class = Map * session ...

  5. MyCat源码分析系列之——配置信息和启动流程

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...

  6. Android进阶系列之源码分析Activity的启动流程

    美女镇楼,辟邪! 源码,是一个程序猿前进路上一个大的而又不得不去翻越障碍,我讨厌源码,看着一大堆.5000多行,要看完得啥时候去了啊.不过做安卓的总有这一天,自从踏上这条不归路,我就认命了.好吧,我慢 ...

  7. Spring Boot启动流程详解(一)

    环境 本文基于Spring Boot版本1.3.3, 使用了spring-boot-starter-web. 配置完成后,编写了代码如下: @SpringBootApplication public ...

  8. linux启动流程及自定义gurb

    linux 启动流程 POST BIOS(boot sequence) 所选择的启动设备次序的MBR中是否有引导程序, ----> MBR(bootloader) 提供内核列表 -------& ...

  9. linux启动流程

    看了深入理解linux内核一书的最后对linux启动流程的介绍,下面就把我能理解的写一下吧: bios(硬件加电自检POST,寻找第一个启动设备) the boot loader(可以从硬盘启动也可以 ...

随机推荐

  1. 201871010104-陈园园《面向对象程序设计(java)》第十六周学习总结

    201871010104-陈园园<面向对象程序设计(java)>第十六周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...

  2. 怎么删掉xampp文件夹

    删掉xampp文件夹时,提示:操作无法完成,因为其中的文件夹或文件已在另一程序中打开 具体的解决方法: 菜单栏输入:服务 找到apachezt和mysqlzt,并禁用    -- 因为之前打开Zent ...

  3. OpenStack Nova

    OpenStack Nova 简介 OpenStack 中的 Nova 负责维护和管理云环境的计算资源 Nova 在现有 Linux 服务器上作为一组守护线程来提供服务 Nova 由多个服务器进程组成 ...

  4. leetcode622. 设计循环队列

    设计你的循环队列实现. 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环.它也被称为“环形缓冲器”. 循环队列的一个好处是我们可以利用这个队列 ...

  5. CSP-S2019游记 执枪的人,一定要做好被杀的觉悟。

    啊,大概是人生中最镇定的三天了. 是了. Day0 教练超级巨,给了我们电话说出去要散养,有事别慌,打电话.身份证丢了别慌,打电话.火车误了别慌,打电话... 然后去了就路上颓颓颓.然后过去试机,打了 ...

  6. Linux性能优化实战学习笔记:第三十三讲

    一.上节回顾 前几节,我们一起学习了文件系统和磁盘 I/O 的工作原理,以及相应的性能分析和优化方法.接下来,我们将进入下一个重要模块—— Linux 的网络子系统. 由于网络处理的流程最复杂,跟我们 ...

  7. .Net Core 最简洁的约定式依赖注入

    .Net Core 最简洁的约定式依赖注入 github:https://github.com/280780363/guc/tree/master/src/Guc.Kernel/Dependency ...

  8. k8s之系统组件架构-02

    k8s系统架构图 网络组件:calico+kube-proxy(IPVS) 网络暴露:traefik+ingress,分别对HTTP与TCP的服务暴露 存储:glusterfs(heketi管理) 日 ...

  9. XC7K325TFFG900 Device 内部结构图

  10. Python 遍历目录下的子目录和文件

    import os A: 遍历目录下的子目录和文件 for root,dirs ,files in os.walk(path) root:要访问的路径名 dirs:遍历目录下的子目录 files:遍历 ...