Flask路由机制
Flask帅气的Hello World示例让人印象深刻。其中使用装饰器的路由写法功不可没,甚至以前看过一个系列没那么神奇,讲了app.route。是的,它好像并没有那么神奇,我也来解析一下😆
Flask的路由是对werkzeug进行了简单的包装,做成了app.route装饰器和blueprint。其实werkzeug中有一段docstring就能反映出flask的路由机制(MapAdapter.dispatch)
1 | from werkzeug.wrappers import Request, Response |
werkzeug路由的全部实现都在routing.py文件当中,看它就够了。路由的基本功能可以说是
- 给出一个URL,匹配到它对应的处理函数,
- 给出函数能反推构造出URL
Rule
一个Rule就是一条URL匹配规则,它的模式如下<converter(arguments):name>
,使用正则匹配找出我们需要的信息
1 | _rule_re = re.compile(r''' |
大概是这么个样子rule匹配
convert规则匹配
很容易想到使用这2个正则能够匹配一个URL,一看他是否匹配某个rule,其次如果匹配那么提取出rule中的参数和值,比如/<int:id>
就能匹配/16
这个url且得到id=16。这2个函数就是match和build了
仅仅有它们是不够的。我们需要的不仅仅是一条Rule,我们需要的是所有Rule的集合。当我们每创建一个Rule的时候将它加入到集合中。所以它提供了get_rules和bind方法
Rule对象同时有2个非常重要的属性endpoint和_regex(编译后的正则).endpoint不用说匹配返回它,根据它返回构建好的URL,匹配功能则是由_regex对象提供的。另外有几个对象继承自Rule
1 | Subdomain self.subdomain |
可以看出这三个对象都是对Rule的属性进行了小幅度修改,其中subdomain和submount修改的最终结果就是改变了_regex对象
说一下Subdomain,子域名。这玩意儿好像并不常用,一般一个子域名就是单独一个项目了,不大会几个子域名混合在一个项目里面。如果要使用子域名需要设置SERVER_NAME。示例如下
1 | from flask import Flask |
然后就是在host里面添加就可以正常调试子域名了
1 | 127.0.0.1 app.local |
Map
Map的作用就是将所有的Rule组合在一起,所以add函数是必须的,Rule对象中的match和build都只是针对自己的,Map中提供的match和build则是针对所有Rule的。比如在Flask中写app.route必然会调用add,将该Rule添加到Map中。代码如下(0.1版本)
1 | def add_url_rule(self, rule, endpoint, **options): |
代码中self.url_map = Map(),可以看到app.route做了2件事情,加入到Map中,加入到了view_functions字典中(endpoint对应函数,这段代码和文章开头的代码是很相似的)
Map对象中有2个属性也是比较重要的,_rules列表和_rules_by_endpoint字典。前者存放所有Rule对象,匹配的时候用来遍历,后者为endpoint到Rule的映射。用来build重组URL
现在再来看看Flask中的dispatch分发逻辑
1 | def dispatch_request(self): |
匹配请求得到endpoint再从view_function中得到对应的处理函数执行
Blueprint
@app.route虽然够炫酷,可是它绝不适应于大项目。因为注册的endpoint就是函数的名称,在大项目中绝对会存在重名函数的。Blueprint的解决办法就是
1 | blueprint = Blueprint('public', __name__, static_folder='../static') |
这样它注册的endpoint就会变成public.index,解决了重名问题
有人问为什么endpoint不直接使用对应的函数,而要使用字符串。我认为是使用url_for等构建URL函数的时候需要它是字符串,因为使用装饰器后func = deactor(func),你使用@app.route之后注册的func和你现在的func不是同一个对象。构建函数将无法使用,这也告诉了我们,任何时候其他的装饰器都需要写在@app.route下面,因为如果写在上面,你注册的那个函数是不包含上面的装饰器的,注定了上面的装饰器无法执行