Flask http://docs.jinkan.org/docs/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 相关文章放出来, 敬请期待!!!
来源: https://juejin.im/post/5bb32f046fb9a05d0e2e7748