The flask object implements a WSGI application and acts as the central
object. It is passed the name of the module or package of the
application. Once it is created it will act as a central registry for
the view functions, the URL rules, template configuration and much more.
The name of the package is used to resolve resources from inside the
package or the folder the module is contained in depending on if the
package parameter resolves to an actual python package (a folder with
an __init__.py file inside) or a standard module (just a .py file).
The map class stores all the URL rules and some configuration
parameters. Some of the configuration values are only stored on the
Map instance since those affect all rules, others are just defaults
and can be overridden for each rule. Note that you have to specify all
arguments besides the rules as keyword arguments!
def run(self, host=None, port=None, debug=None, **options):
from werkzeug.serving import run_simple
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
if debug is not None:
self.debug = bool(debug)
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
try:
run_simple(host, port, self, **options)
finally:
# reset the first request information if the development server
# reset normally. This makes it possible to restart the server
# without reloader and that stuff from an interactive shell.
self._got_first_request = False
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
A Rule represents one URL pattern. There are some options for Rule that change the way it behaves and are passed to the Rule constructor.
一个 Rule 对象代表了一种 URL 模式,可以通过传入参数来改变它的许多行为。
Rule 的 __init__ 方法为:
def __init__(self, string, defaults=None, subdomain=None, methods=None,
build_only=False, endpoint=None, strict_slashes=None,
redirect_to=None, alias=False, host=None):
if not string.startswith('/'):
raise ValueError('urls must start with a leading slash')
self.rule = string
self.is_leaf = not string.endswith('/')
self.map = None
self.strict_slashes = strict_slashes
self.subdomain = subdomain
self.host = host
self.defaults = defaults
self.build_only = build_only
self.alias = alias
if methods is None:
self.methods = None
else:
if isinstance(methods, str):
raise TypeError('param `methods` should be `Iterable[str]`, not `str`')
self.methods = set([x.upper() for x in methods])
if 'HEAD' not in self.methods and 'GET' in self.methods:
self.methods.add('HEAD')
self.endpoint = endpoint
self.redirect_to = redirect_to
if defaults:
self.arguments = set(map(str, defaults))
else:
self.arguments = set()
self._trace = self._converters = self._regex = self._weights = None
def bind(self, map, rebind=False):
"""Bind the url to a map and create a regular expression based on
the information from the rule itself and the defaults from the map.
:internal:
"""
if self.map is not None and not rebind:
raise RuntimeError('url rule %r already bound to map %r' %
(self, self.map))
self.map = map
if self.strict_slashes is None:
self.strict_slashes = map.strict_slashes
if self.subdomain is None:
self.subdomain = map.default_subdomain
self.compile()
本文简单的分析了 Flask 的源码,主要关注 WSGI、Flask 对象的数据结构、Flask 应用启动过程、请求处理过程、视图函数、URL 的映射、应用上下文和请求上下文。
这是 Flask 官方钦定的 Demo 代码:
这篇文章从这个简单的代码开始,简要介绍了 WSGI、Flask 对象的数据结构、Flask 应用启动过程、请求处理过程、视图函数、URL 的映射、request 和 response 类(应用上下文和请求上下文),这些主题涵盖了一个 web 框架的核心。
WSGI
在用户发起的请求到达服务器之后,会被一个 HTTP 服务器所接收,然后交给 web 应用程序做业务处理,这样 HTTP 服务器和 web 应用之间就需要一个接口,在 Python web 开发的世界里,Python 官方钦定了这个接口并命名为 WSGI,由 PEP333 所规定。只要服务器和框架都遵守这个约定,那么就能实现服务器和框架的任意组合。按照这个规定,一个面向 WSGI 的框架必须要实现这个方法:
在工作过程中,HTTP 服务器会调用上面这个方法,传入请求信息,即名为
environ
的字典和start_response
函数,应用从environ
中获取请求信息,在进行业务处理后调用start_response
设置响应头,并返回响应体(必须是一个可遍历的对象,比如列表、字典)给 HTTP 服务器,HTTP 服务器再返回响应给用户。所以 Flask 作为一个开发 web 应用的 web 框架,负责解决的问题就是:
__call__
方法下面就来看看 Flask 是如何解决这些问题的。
应用的创建
Demo 代码的第二行创建了一个 Flask 类的实例,传入的参数是当前模块的名字。我们先来看看 Flask 应用到底是什么,它的数据结构是怎样的。
Flask
是这样一个类:Flask 类有这样一些属性:
request_class = Request
设置请求的类型response_class = Response
设置响应的类型这两个类型都来源于它的依赖库
werkzeug
并做了简单的拓展。Flask 对象的
__init__
方法如下:到这里一个 Flask 对象创建完毕并被变量
app
所指向,其实它就是一个保存了一些配置信息,绑定了一些视图函数并且有个 URL 映射对象(url_map
)的对象。但我们还不知道这个 Map 对象是什么,有什么作用,从名字上看,似乎其作用是映射 URL 到视图函数。源代码第 21 行有from werkzeug.routing import Map, Rule
,那我们就来看看werkzeug
这个库中对 Map 的定义:可以看到这个类的对象储存了所有的 URL 规则和一些配置信息。由于
werkzeug
的映射机制比较复杂,我们下文中讲到映射机制的时候再深入了解,现在只要记住 Flask 应用(即一个Flask
类的实例)存储了视图函数,并通过url_map
这个变量存储了一个 URL 映射机构就可以了。应用启动过程
Demo 代码的第 6 行是一个限制,表示如果 Python 解释器是直接运行该文件或包的,则运行 Flask 程序:在 Python 中,如果直接执行一个模块或包,那么解释器就会把当前模块或包的
__name__
设置为为__main_
。第 7 行中的
run
方法启动了 Flask 应用:可以看到这个方法基本上是在配置参数,实际上启动服务器的是
werkzeug
的run_simple
方法,该方法在默认情况下启动了服务器BaseWSGIServer
,继承自 Python 标准库中的HTTPServer.TCPServer
。注意在调用run_simple
时,Flask 对象把自己self
作为参数传进去了,这是正确的,因为服务器在收到请求的时候,必须要知道应该去调用谁的__call__
方法。按照标准库中
HTTPServer.TCPServer
的模式,服务器必须有一个类来作为 request handler 来处理收到的请求,而不是由HTTPServer.TCPServer
本身的实例来处理,werkzeug
提供了WSGIRequestHandler
类来作为 request handler,这个类在被BaseWSGIServer
调用时,会执行这个函数:函数的第一行就是按照 WSGI 要求的,调用了 app 并把
environ
和start_response
传入。我们再看看 flask 中是如何按照 WSGI 要求对服务器的调用进行呼应的。可以看到 Flask 按照 WSGI 的要求实现了
__call__
方法,因此成为了一个可调用的对象。但它不是在直接在__call__
里写逻辑的,而是调用了wsgi_app
方法,这是为了中间件的考虑,不展开谈了。这个方法返回的response(environ, start_response)
中,response
是werkzueg.response
类的一个实例,它也是个可以调用的对象,这个对象会负责生成最终的可遍历的响应体,并调用start_response
形成响应头。请求处理过程
wsgi_app
方法中里面的内容就是对请求处理过程的一个高度抽象。首先,在接收到服务器传过来的请求时,Flask 调用
request_context
函数建立了一个RequestContext
请求上下文对象,并把它压入_request_ctx_stack
栈。关于上下文和栈的内容下文会再讲到,你现在需要知道的是,这些操作是为了 flask 在处理多个请求的时候不会混淆。之后,Flask 会调用full_dispatch_request
方法对这个请求进行分发,开始实际的请求处理过程,这个过程中会生成一个响应对象并最终通过调用response
对象来返回给服务器。如果当中出错,就声称相应的错误信息。不管是否出错,最终 Flask 都会把请求上下文推出栈。full_dispatch_request
是请求分发的入口,我们再来看它的实现:首先调用
try_trigger_before_first_request_functions
方法来尝试调用before_first_request
列表中的函数,如果Flask
的_got_first_request
属性为False
,before_first_request
中的函数就会被执行,执行一次之后,_got_first_request
就会被设置为True
从而不再执行这些函数。然后调用
preprocess_request
方法,这个方法调用before_request_funcs
列表中所有的方法,如果这些before_request_funcs
方法中返回了某种东西,那么就不会真的去分发这个请求。比如说,一个before_request_funcs
方法是用来检测用户是否登录的,如果用户没有登录,那么这个方法就会调用abort
方法从而返回一个错误,Flask 就不会分发这个请求而是直接报 401 错误。如果
before_request_funcs
中的函数没有返回,那么再调用dispatch_request
方法进行请求分发。这个方法首先会查看 URL 规则中有没有相应的endpoint
和value
值,如果有,那么就调用view_functions
中相应的视图函数(endpoint
作为键值)并把参数值传入(**req.view_args
),如果没有就由raise_routing_exception
进行处理。视图函数的返回值或者错误处理视图函数的返回值会返回给wsgi_app
方法中的rv
变量。然后 Flask 就会根据
rv
生成响应,这个make_response
方法会查看 rv 是否是要求的返回值类型,否则生成正确的返回类型。比如 Demo 中返回值是字符串,就会满足isinstance(rv, basestring)
判断并从字符串生成响应。这一步完成之后,Flask 查看是否有后处理视图函数需要执行(在process_response
方法中),并最终返回一个完全处理好的response
对象。视图函数注册
在请求处理过程一节中,我们已经看到了 Flask 是如何调用试图函数的,这一节我们要关注 Flask 如何构建和请求分派相关的数据结构。我们将主要关注
view_functions
,因为其他的数据结构如before_request_funcs
的构建过程大同小异,甚至更为简单。我们也将仔细讲解在应用的创建一节中遗留的问题,即url_map
到底是什么。Demo 代码的第 4 行用修饰器
route
注册一个视图函数,这是 Flask 中受到广泛称赞的一个设计。在 Flask 类的route
方法中,可以看到它调用了add_url_rule
方法。这个方法负责注册视图函数,并实现 URL 到视图函数的映射。首先,它要准备好一个视图函数所支持的 HTTP 方法(基本上一半多的代码都是在做这个),然后通过
url_rule_class
创建一个rule
对象,并把这个对象添加到自己的url_map
里。我们那个遗留问题在这里就得到解答了:rule
对象是一个保存合法的(Flask 应用所支持的) URL、方法、endpoint
(在**options
中) 及它们的对应关系的数据结构,而url_map
是保存这些对象的集合。然后,这个方法将视图函数添加到view_functions
当中,endpoint
作为它的键,其值默认是函数名。我们再来深入了解一下
rule
,它被定义在werkzeug.routing.Rule
中:Rule 的
__init__
方法为:一个 Rule 被创建后,通过
Map
的add
方法被绑定到Map
对象上,我们之前说过flask.url_map
就是一个Map
对象。而
Rule
的bind
方法的内容,就是添加Rule
对应的Map
,然后调用compile
方法生成一个正则表达式,compile
方法比较复杂,就不展开了。在 Flask 应用收到请求时,这些被绑定到
url_map
上的Rule
会被查看,来找到它们对应的视图函数。这是在请求上下文中实现的,我们先前在dispatch_request
方法中就见过——我们是从_request_ctx_stack.top.request
得到rule
并从这个rule
找到endpoint
,最终找到用来处理该请求的正确的视图函数的。所以,接下来我们需要看请求上下的具体实现,并且看一看 Flask 是如何从url_map
中找到这个rule
的。请求上下文
请求上下文是如何、在何时被创建的呢?我们先前也见过,在服务器调用应用的时候,Flask 的
wsgi_app
中有这样的语句,就是创建了请求上下文并压栈。request_context
方法非常简单,就是创建了RequestContext
类的一个实例,这个类被定义在flask.ctx
文件中,它包含了一系列关于请求的信息,最重要的是它自身的request
属性指向了一个Request
类的实例,这个类继承自werkzeug.Request
,在RequestContext
的创建过程中,它会根据传入的environ
创建一个werkzeug.Request
的实例。接着
RequestContext
的push
方法被调用,这个方法将自己推到_request_ctx_stack
的栈顶。_request_ctx_stack
被定义在flask.global
文件中,它是一个LocalStack
类的实例,是werkzeug.local
所实现的,如果你对 Python 的 threading 熟悉的话,就会发现这里实现了线程隔离,就是说,在 Python 解释器运行到_request_ctx_stack
相关代码的时候,解释器会根据当前进程来选择正确的实例。但是,在整个分析 Flask 源码的过程中,我们也没发现 Flask 在被调用之后创建过线程啊,那么为什么要做线程隔离呢?看我们开头提到的
run
函数,其实它可以传一个threaded
参数。当不传这个函数的时候,我们启动的是BasicWSGIServer
,这个服务器是单线程单进程的,Flask 的线程安全自然没有意义,但是当我们传入这个参数的时候,我们启动的是ThreadedWSGIServer
,这时 Flask 的线程安全就是有意义的了,在其他多线程的服务器中也是一样。总结
一个请求的旅程
这里,我们通过追踪一个请求到达服务器并返回(当然是通过“变成”一个相应)的旅程,串讲本文的内容。
environ
和make_response
函数,然后调用了自己身上注册的 Flask 应用。application(environ, make_response)
方法。在 Flask 中,这个方法是个被__call__
中转的叫做wsgi_app
的方法。它首先通过environ
创建了请求上下文,并将它推入栈,使得 flask 在处理当前请求的过程中都可以访问到这个请求上下文。before_first_request_funcs
before_request_funcs
view_functions
中的函数,并最终通过finalize_request
生成一个response
对象,当中只要有函数返回值,后面的函数组就不会再执行,after_request_funcs
进行response
生成后的后处理。response
对象,最终调用了make_response
函数,并返回了一个可遍历的响应内容。Flask 和 werkzeug
在分析过程中,可以很明显地看出 Flask 和
werkzeug
是强耦合的,实际上werkzeug
是 Flask 唯一不可或缺的依赖,一些非常细节的工作,其实都是werkzeug
库完成的,在本文的例子中,它至少做了这些事情:Response
和Request
类型供 Flask 使用,在实际开发中,我们在请求和响应对象上的操作,调用的其实是werkzeug
的方法。_request_ctx_stack
对 Flask 实现线程保护。对于 Flask 的源码解析先暂时到这里。有时间的话,我会分析 Flask 中的模板渲染、
import request
、蓝图和一些好用的变量及函数,或者深入分析werkzeug
库。参考阅读