框架简介
Flask 是由 python 实现的一个 web 微框架, 有对应的 python3 及 python2 版本.
非常适用于小型网站, 非常适用于开发 web 服务的 API,
开发大型网站无压力, 但代码架构需要自己设计, 开发成本取决于开发者的能力和经验.
Flask 自由、灵活, 可扩展性强, 第三方库的选择面广, 开发时可以结合自己最喜欢用的轮子, 也能结合最流行最强大的 Python 库.
个人也将 Flask 作为 web 开发的首选框架.
这是一个 Flask 的 hello world 例子, 访问浏览器主页将输出 “hello world”.
1
2
3
4
5
6
7
8
9
10
11
|
from flask import Flask
app = Flask(__name__)
@app.route('/', methods=['GET'])
def get_hello_world():
return "hello world"
app.run(port=8080)
|
Flask 的 handler 以函数为单位进行组织, 通过 @app.route
装饰器把 handler 注册到路由表, app.run(port=8080)
监听本地 8080,
一个简单的 api 服务就搭建完成了.
如果要满足商业应用的开发, 可以根据实际需要, 选择 Flask 的已有组件, 或者其他丰富的 Python 库, 搭积木式开发模式让开发者得心应手.
路由注册过程
- @app.route(’/’, methods=[‘GET’])
装饰器的定义:
1
2
3
4
5
6
|
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 函数实现的, 最终注册在 app.view_functions 字典中.
key 是 endpoint (默认为函数名), value 是 view_func (handler 函数引用).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
methods = options.pop('methods', None)
# if the methods are not given and the view_func object knows its
# methods we can use that instead. If neither exists, we go with
# a tuple of only ``GET`` as default.
if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
if isinstance(methods, string_types):
raise TypeError('Allowed methods have to be iterables of strings, '
'for example: @app.route(..., methods=["POST"])')
methods = set(item.upper() for item in methods)
# Methods that should always be added
required_methods = set(getattr(view_func, 'required_methods', ()))
# Add the required methods now.
methods |= required_methods
rule = self.url_rule_class(rule, methods=methods, **options)
self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an '
'existing endpoint function: %s' % endpoint)
self.view_functions[endpoint] = view_func
|
值得注意的是 url 与 view_func 并非是直接映射的关系, 中介桥梁是 endpoint, url -> endpoint -> view_func.
rule = self.url_rule_class(rule, methods=methods, **options)
将 rule 和 endpoint 绑定.
我们看看 self.url_map.add(rule)
的细节.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Rule(RuleFactory):
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):
# url path
self.rule = string
# app.view_functions key
self.endpoint = endpoint
# methods: "GET", "POST" ...
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')
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Map(object):
def __init__(self):
self._rules = []
self._rules_by_endpoint = {}
self._remap = True
def add(self, rulefactory):
for rule in rulefactory.get_rules(self):
rule.bind(self)
self._rules.append(rule)
self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
self._remap = True
|
上述代码在 app.url_map._rules 保存了 url_rule_class (url -> endpoint 的绑定关系)
路由查找过程
这是简化的 Flask 响应处理过程伪代码, 路由的查找过程发生在 dispatch_request():match_request() 函数中.
1
2
3
4
5
6
|
def wsgi_app(environ, start_response):
with RequestContext(environ):
rv = dispatch_request()
response = make_response(rv)
response = process_response(response)
return response(environ, start_response)
|
1
2
3
|
def dispatch_request(self):
url_rule, view_args = self.match_request()
return self.view_functions[url_rule.endpoint](**view_args)
|
具体的路由查找匹配过程代码如下:
(1) 从 app.url_map._rules 中遍历出 rule (url endpoint 绑定的类)
(2) 通过 url 的路径 path 正则匹配查找
(3) 返回匹配的 rule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class MapAdapter(object):
def match(self, path_info=None, method=None, query_args=None):
have_match_for = set()
for rule in self.map._rules:
rv = rule.match(path)
if rv is None:
continue
if rule.methods is not None and method not in rule.methods:
have_match_for.update(rule.methods)
continue
return rule, rv
if have_match_for:
raise MethodNotAllowed(valid_methods=list(have_match_for))
raise NotFound()
|
1
2
3
4
5
6
7
|
class Rule(RuleFactory):
def match(self, path, method=None):
m = self._regex.search(path)
if m is not None:
return {}
return None
|
小结
Flask 的路由有两部分组成, 一个是 view_functions 字典, 存储着函数名 endpoint 和函数引用 view_func 一一对应关系,
另一个部分是 url_map, 存储的 rules 数组, 数组成员是 Rule 对象, 记录着 url 和 endpoint 的一一对应关系.
通过遍历数组 app.map._rules, 正则匹配查找 url, 最终调用 view_func.