本篇介绍 Flask 路由的基本用法,并且通过部分源代码深入浅出阐述 Flask 路由的实现机制。
路由的基本用法
我们先编写一段简单代码,代码包括两个视图函数。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Index Page'
@app.route('/about')
def about():
return 'About Page'
if __name__ == '__main__':
print(app.url_map)
app.run()
所谓路由,就是 Flask 根据客户端 request 的 URL,查找对应的视图函数 (view function),由视图函数进行处理后,返回 response 到客户端。Flask application 有两个属性来保存与路由相关的信息:
-
url_map
: 储存 url 和 endpoint 的映射(url_map 的数据类型是werkzeug.routing.Map
) -
view_functions
: 储存 endpoint 和 view function 的映射 (dict
类型)
就上面的例子来说,当客户端请求的 URL 中,path 为 /
,Flask 就用 index
视图函数进行处理,当客户端请求的 URL 中,path 为 /about
, Flask 就用 about
视图函数进行处理。运行程序,在 IDE 中打印了如下信息:
Map([<Rule '/about' (GET, OPTIONS, HEAD) -> about>,
<Rule '/' (GET, OPTIONS, HEAD) -> index>,
<Rule '/static/<filename>' (GET, OPTIONS, HEAD) -> static>])
endpoint 的作用
这里有两个知识点:第一个是 url 到视图函数的映射以 endpoint 来作为中介 (url -> endpoint -> view function)。为什么从 url 到视图函数的映射使用 endpoint 作为中介呢?如果不用 blueprint,endpoint 是没什么作用的。使用 blueprint 后,endpoint 就允许通过 blueprint 来进行区分。
接下来说明 endpoint 的作用,创建一个新的 Flask 工程,工程文件的结构如下:
flask-route-logic /
admin /
__init__.py
user /
__init__.py
app.py
admin 和 user 作为两个蓝图 (blueprint),用于模块化组织代码。
admin/__init__.py
的代码如下:
# admin/__init__.py
from flask import Blueprint
adminbp = Blueprint('adminbp', __name__, url_prefix='/admin')
@adminbp.route('/')
def index():
return 'Admin blueprint, index page'
user/__init__.py
的代码如下:
# user/__init__.py
from flask import Blueprint
userbp = Blueprint('userbp', __name__, url_prefix='/user')
@userbp.route('/')
def index():
return 'User blueprint, index page'
app 主文件的代码如下:
from flask import Flask, url_for
from user import userbp
from admin import adminbp
app = Flask(__name__)
app.register_blueprint(userbp)
app.register_blueprint(adminbp)
@app.route('/', endpoint='index')
def index():
return 'Index Page'
if __name__ == '__main__':
print(app.url_map)
app.run()
在代码中,一共定义了 3 个名称都为 index
的视图函数。运行后,打印的 url_map
信息如下:
Map([<Rule '/admin/' (OPTIONS, HEAD, GET) -> adminbp.index>,
<Rule '/user/' (OPTIONS, HEAD, GET) -> userbp.index>,
<Rule '/' (OPTIONS, HEAD, GET) -> index>,
<Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])
可以看到,使用 blueprint 后,3 个 index 函数,endpoint 的名称分别为 index, adminbp.index 和 userbp.index,这样使用 url_for()
函数的时候就能区分,进行反向解析了。
定位 static 文件:static endpoint
第二个知识点,在本篇第一段代码中,我们定义了两个路由,为什么打印出来的 url_map 却有 3 个 rule (/
, /about
和 static
) 呢?这是因为 Flask 在代码中添加了一个名为 static 的 endpoint,用于 url_for() 函数定位 static文件, 比如 css, images 等等。为了便于理解,我们用示例代码来说明。我们搭建一个如下所示的工程文件结构:
flask-route-logic /
static /
images /
demo.png
templates /
index.html
app2.py
在 static/imgage 文件夹下有一个图片文件,我们在 index.html 中,将使用 url_for 函数来构建一个 url,指向 demo.png 图像文件。
index.html 文件代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>url_for 函数示例</title>
</head>
<body>
<p1>Below is a plus size model:</p1><br/>
<img src="{{ url_for('static', filename='images/demo.png') }}"/>
</body>
</html>
app2.py 的代码:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
这里,之所以能用 url_for('static', filename='images/demo.png
) ,把 static
作为 endpoint, 就是因为 Flask 为了处理静态文件而在代码中增加的一个 endpoint ( static) 的路由匹配规则。在 Flask 的源代码 __init__()
方法中,我们可以看到这样一段代码(不同版本可能稍有出入):
if self.has_static_folder:
# ...(省略无关代码)
self.add_url_rule(
self.static_url_path + "/<path:filename>",
endpoint="static",
host=static_host,
view_func=self.send_static_file,
)
这段代码用硬编码的方式添加了 static 这个 endpoint。
Flask 的路由通过 @route
装饰器实现,本质上是调用 add_url_urle()
方法实现的,相关代码如下:
# flask/app.py
class Flask(_PackageBoundObject):
# ...
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
add_url_rule()
函数的 3 个参数是 rule, endpoint 和 view functions,其核心代码如下(我省略了无关代码和部分细节代码):
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
# 如果没有指定endpoint,则默认为view functon的函数名
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
# 将 endpoint 加入到 options(dict)
options["endpoint"] = endpoint
# methods: GET, HEAD, OPTIONS等
methods = options.pop("methods", None)
# 从view function获取method,没有则为GET
if methods is None:
methods = getattr(view_func, "methods", None) or ("GET",)
# 将 methods改变为set类型
methods = set(item.upper() for item in methods)
# 将rule添加到url_map
rule = self.url_rule_class(rule, methods=methods, **options)
self.url_map.add(rule)
# 添加view functions
if view_func is not None:
self.view_functions[endpoint] = view_func
根据上面的核心代码,我们知道,add_url_rule()
最主要做了 4 件事:
- 处理 endpoint: 由函数参数提供,或者默认为函数名称
- 处理 methods (GET, HEAD, OPTIONS, POST 等)
- 将每个匹配规则作为 rule 添加到 url_map
- 将 endpoint 和 view function 的映射添加到 url_functions (dict)
既然 route 装饰器的本质是调用 add_url_rule()
,我们的代码也可以这样写:
from flask import Flask
app = Flask(__name__)
def index():
return 'Index Page'
def about():
return 'About Page'
if __name__ == '__main__':
app.add_url_rule('/', 'index', index)
app.add_url_rule('/about', 'about', about)
print(app.url_map)
app.run()
另外,说明一下,Flask 路由的数据结构、路由匹配规则等,是由 werkzeug 实现的,Flask 只是使用者而已。
网友评论