Flask 源码阅读笔记 开篇

72次阅读

共计 4263 个字符,预计需要花费 11 分钟才能阅读完成。

Flask 是一个 Python 实现的 Web 开发微框架, 有丰富的生态资源。本文从一段官方的示例代码通过一步步打断点方式解释 Flask 内部的运行机制,在一些关键概念会有相关解释,这些前提概念对整体理解 Flask 框架十分重要,本文基于 flask 0.1 版本进行相应的分析。

官方 demo 示例
from flask import Flask
app = Flask(__name__)

@app.route(‘/’)
def hello_world():
return ‘Hello World!’

if __name__ == ‘__main__’:
app.run()
第一行 import Flask 类对象,这个无需解释。跳到第二行,使用当前模块的名字传入 Flask 类中,并实例化 Flask 对象,我们在这个地方打个断点,看看 Flask 类别里有什么。上图可以看出,Flask 类中定义 jinia_options、request_class、response_class 等属性,这里我们不关系具体作用,先看看源码中 Flask 是不是定义了这些属性。
class Flask(object):
# 省略了注释部分
# flask 用作请求对象的类
request_class = Request
# flask 用作响应对象的类
response_class = Response
# 静态文件路径
static_path = ‘/static’
# 密钥,用于加密 session 或其它涉及安全的东西
secret_key = None
#存储 session 对象数据的 cookie 名称
session_cookie_name = ‘session’
# Jinja2 环境的一些选项
jinja_options = dict(
autoescape=True,
extensions=[‘jinja2.ext.autoescape’, ‘jinja2.ext.with_’]
)
这部分是初始化 Flask 类中默认设置的一些属性,其实通过名字也可以大概知道每个属性的作用。看到这个地方时是不是一脸懵逼,Request、Response 是什么东西,有什么作用?Jinja2 又是什么东西?别急,下面慢慢解释这几个东西的作用。
Request && Response
from werkzeug import Request as RequestBase, Response as ResponseBase

class Request(RequestBase):
“””The request object used by default in flask. Remembers the
matched endpoint and view arguments.

It is what ends up as :class:`~flask.request`. If you want to replace
the request object used you can subclass this and set
:attr:`~flask.Flask.request_class` to your subclass.
“””

def __init__(self, environ):
RequestBase.__init__(self, environ)
self.endpoint = None # 请求对象的端点
self.view_args = None # 请求视图函数的参数

class Response(ResponseBase):
“””The response object that is used by default in flask. Works like the
response object from Werkzeug but is set to have a HTML mimetype by
default. Quite often you don’t have to create this object yourself because
:meth:`~flask.Flask.make_response` will take care of that for you.

If you want to replace the response object used you can subclass this and
set :attr:`~flask.Flask.request_class` to your subclass.
“””
default_mimetype = ‘text/html’
通过源码的注释我们可以知道,Request、Response 都只是对 werkzeug 库的 Request、Response 进行了一层包装并加入一些属性。先说一下它们的作用:

Request 的作用:是 flask 默认的请求对象,用来记住匹配的 endpoint(端点)和 view arguments( 视图参数)
Response 的作用:是 flask 默认的响应对象,默认设置 MIME 类型默认设置为 HTML(即是定义了内容类型 Content-Type 返回的类型为 HTML), 默认情况下,你不用自己创建这个对象,因为下面的 make_response 函数会帮你处理。

看完上面源码和解释,是不是有新的疑问了,werkzeug 又是什么?端点又是什么概念?额,werkzeug 的作用真的很大,整个框架都是基于它实现的,下面会有一个部分专门说明这个库。说明:werkzeug 库和 jinja2 是 flask 的两个依赖库,会分出一篇文章专门介绍,这篇文章重点是整个 Flask 内部的机制,建议看到对应部分,先提前去读两个依赖库的文章。
接下来继续下一步的调试,初始化一个 Flask 类, 先看看 Flask 类的初始化函数:

def _get_package_path(name):
“””Returns the path to a package or cwd if that cannot be found.”””
# 获取 模块包 路径,被 Flask 引用
try:
return os.path.abspath(os.path.dirname(sys.modules[name].__file__))
except (KeyError, AttributeError):
return os.getcwd()

class Flask(object):
# 简化版,已经去掉注释,建议看源码注释加上这个理解
def __init__(self, package_name):
# 设置是否开启调试模式,若开启,会监视项目代码变化,
# 开发服务器重载 Flask 应用
self.debug = False

# 包或模块的名字,模块的名称将会因其作为单独应用启动还是作为模块导入而不同
# Flask 才知道到哪去找模板、静态文件
self.package_name = package_name

# 根据 Flask 传入的__name__, 找到项目的根路径
self.root_path = _get_package_path(self.package_name)

# 已注册的所有视图函数的字典,字典的键是函数名称,可以用来生成 URL(url_for 函数)
# 字典的值是函数本身, 想要注册视图函数,可以使用 route 装饰器
self.view_functions = {}

# 所有已注册错误处理程序的字典, 字典的键是一个整数类型(integer) 的错误码
# 字典的值是对应错误的函数,想要注册错误 handler, 可以使用 errorhandler 装饰器
self.error_handlers = {}

# 请求开始进入时,但还请求还没调度前调用的函数列表,也就是预处理操作
# 可用于打开数据库连接或获取当前登录用户,使用 before_route 装饰器注册
self.before_request_funcs = []

# 请求结束时调用的函数列表,这些函数会被传入当前响应对象并将其修改或替换它。
self.after_request_funcs = []

# 不带参数调用的函数列表,用于填充模板上下文,每个应该返回更新模板上下文的字典
# 默认的处理器用来注入 session、request 和 g
self.template_context_processors = [_default_template_ctx_processor]

# 使用 werkzeug 的 routing.Map, 用于给应用增加一些 URL 规则,
# URL 规则形成一个 Map 实例的过程中会生成对应的正则表达式,可以进行 URL 匹配
self.url_map = Map()

# 添加静态文件的 URL 映射规则
# SharedDataMiddleware 中间件用来为程序添加处理静态文件的能力
if self.static_path is not None:
self.url_map.add(Rule(self.static_path + ‘/<filename>’,
build_only=True, endpoint=’static’))
if pkg_resources is not None:
target = (self.package_name, ‘static’)
else:
target = os.path.join(self.root_path, ‘static’)
self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
self.static_path: target # URL 路径和实际文件目录(static 文件夹)的映射
})

# Jinja2 环境,它通过 jinja_options 创建,加载器(loader)通过
self.jinja_env = Environment(loader=self.create_jinja_loader(),
**self.jinja_options)
# 将 url_for, get_flashed_message 作为全局对象填充入模板上下文中,可以在模板中调用它们
self.jinja_env.globals.update(
url_for=url_for,
get_flashed_messages=get_flashed_messages
)
上面就是一个 Flask 实例化时所做的工作,其实就是保存了一下配置信息,设置了一下 Jinja2 环境,并定义了一个 URL 映射对象,用于映射 URL 到函数之间的关系。
总结
开篇主要讲了初始化一个 Flask 对象,内部做了什么工作,配置了一下信息,设置了一下 Jinja2 环境,定义了一些视图函数存放的数据结构,定义了一个 Map 对象用于后面保存 URL 和 视图函数的映射关系。接下来会有更多关于 werkzeug, jinja2 和 WSGI 相关文章放出来,敬请期待!!!
参考
flask 文档 flask 项目源码 0.1 版本 flask 注释版

正文完
 0