Werkzeug源码阅读笔记(四)
今天主要讲一下werkzeug
中的routing
模块。这个模块是werkzeug
中的重点模块,Flask
中的路由相关的操作使用的都是这个模块
routing模块的用法
在讲解模块的源码之前,先讲讲这个模块怎么用。
创建Map()对象:
>>> m = Map([
... # Static URLs
... Rule('/', endpoint='static/index'),
... Rule('/about', endpoint='static/about'),
... Rule('/help', endpoint='static/help'),
... # Knowledge Base
... Subdomain('kb', [
... Rule('/', endpoint='kb/index'),
... Rule('/browse/', endpoint='kb/browse'),
... Rule('/browse/<int:id>/', endpoint='kb/browse'),
... Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
... ])
... ], default_subdomain='www')
我们可以看到,一个Map
中以列表的形式包含多个Rule
. 示例里面还有个Subdomain
,除了Subdomain
名外,它里面以列表的形式包含多个Rule
,如果没有Subdomain
,后面的default_subdomain
可以省略(default_subdomain
适配于除了Subdomain
之外的Rule
部分)
在创建了Map的实例后,我们可以为每个Subdomain
创建URL适配器
>>> c = m.bind('example.com')
>>> c.build("kb/browse", dict(id=42)) #如果url有参数,使用dict()里面填参数名和值
'http://kb.example.com/browse/42/'
>>> c.build("kb/browse", dict()) #build接受的参数是endpoint,返回url地址
'http://kb.example.com/browse/'
>>> c.build("kb/browse", dict(id=42, page=3))
'http://kb.example.com/browse/42/3'
>>> c.build("static/about")
'/about'
>>> c.build("static/index", force_external=True)
'http://www.example.com/'
>>> c = m.bind('example.com', subdomain='kb')
>>> c.build("static/about")
'http://www.example.com/about'
部分源码分析
在routing
模块中有个RuleFactory
类,提供了get_rules()
工厂方法,该方法的设计目的是使得URL重用. 所有继承该类的类必须实现该方法
比如Subdomain
类:
class Subdomain(RuleFactory):
def __init__(self, subdomain, rules):
self.subdomain = subdomain
self.rules = rules
def get_rules(self, map):
for rulefactory in self.rules:
for rule in rulefactory.get_rules(map):
rule = rule.empty()
rule.subdomain = self.subdomain
yield rule
在该类中get_rules
方法是一个生成器,调用一次返回一个subdomain
中的rule
,且该Rule未绑定.
同理还有Submount
,EndpointPrefix
类,源码差不多,就不细讲了。只说下怎么用:
Submount
的用法:
url_map = Map([
Rule('/', endpoint='index'),
Submount('/blog', [
Rule('/', endpoint='blog/index'),
Rule('/entry/<entry_slug>', endpoint='blog/show')
])
])
把submount中第一个元素(路径)跟在原路径后,里面的Rule均以这个路径为挂载点
这里当访问'blog/entry/<entry_slug>'
就会找到'blog/show'
这个endpoint
;当访问'blog'
就会找到'blog/index'
这个endpoint
EndpointPrefix
的用法:
url_map = Map([
Rule('/', endpoint='index'),
EndpointPrefix('blog/', [Submount('/blog', [
Rule('/', endpoint='index'),
Rule('/entry/<entry_slug>', endpoint='show')
])])
])
和上一个示例等效,只不过是提取出了所有endpoint
中相同的前缀放在前面
Rule类
在该类的实例中最重要的就是存储了URL地址。以下是它的构造方法的头部:
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)
在该类中,实现了bind
方法,源码如下:
def bind(self, map, rebind=False):
if self.map is not None and not rebind:
raise RuntimeError('url rule %r already bound to map %r' %
(self, self.map))
self.map = map
if self.strict_slashes is None:
self.strict_slashes = map.strict_slashes
if self.subdomain is None:
self.subdomain = map.default_subdomain
self.compile()
该方法的作用就是把Rule
的实例绑定到一个Map
实例上去
Map类
在该类的实例中,存储了很多个Rule类的实例,同时还有部分配置参数。以下是它的构造方法的头部:
def __init__(self, rules=None, default_subdomain='', charset='utf-8',
strict_slashes=True, redirect_defaults=True,
converters=None, sort_parameters=False, sort_key=None,
encoding_errors='replace', host_matching=False)
对照本文中开头的部分,可以发现rules
参数是一个列表,该列表中包含了多个Rule
的实例
在该类中实现了几个重要的方法:
add()
方法:它的作用是把一个Rule的实例添加到该Map实例中,并绑定。源码很简单:
def add(self, rulefactory):
for rule in rulefactory.get_rules(self): #获得rule实例
rule.bind(self) #绑定该实例(源码见上面Rule类中的bind方法)
#在Map的_rules列表中加入该Rule实例,_rules用来装Map初始化函数中rules参数传进的Rules
self._rules.append(rule)
self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
self._remap = True
bind()
方法:返回一个MapAdapter类的实例,该类的作用是用来做URL的匹配。bind
的头部为:
bind(self, server_name, script_name=None, subdomain=None,
url_scheme='http', default_method='GET', path_info=None,
query_args=None)
MapAdapter类
该类用来做URL匹配。
dispatcher()方法:该方法的作用是,传入path_info
,该方法会使用match()
方法找到对应的endpoint
和相关参数,然后再把这个endpoint
作为参数传入view_func
视图函数中,返回一个view_func
对象. 源码如下
def dispatch(self, view_func, path_info=None, method=None,
catch_http_exceptions=False):
try:
try:
endpoint, args = self.match(path_info, method) #获得endpoint和所需参数
except RequestRedirect as e:
return e
return view_func(endpoint, args)
except HTTPException as e:
if catch_http_exceptions:
return e
raise
match()方法:该方法的作用是,传入path_info
和method
,该方法会返回对应的endpoint
和路径包含的参数,比如:
>>> m = Map([
... Rule('/', endpoint='index'),
... Rule('/downloads/', endpoint='downloads/index'),
... Rule('/downloads/<int:id>', endpoint='downloads/show')
... ])
>>> urls = m.bind("example.com", "/") #urls是MapAdapter对象
>>> urls.match("/", "GET")
('index', {})
>>> urls.match("/downloads/42")
('downloads/show', {'id': 42})
build()方法:该方法与match()
对应,传入endpoint
和对应参数,返回path_info
. 用法如下
>>> m = Map([
... Rule('/', endpoint='index'),
... Rule('/downloads/', endpoint='downloads/index'),
... Rule('/downloads/<int:id>', endpoint='downloads/show')
... ])
>>> urls = m.bind("example.com", "/")
>>> urls.build("index", {})
'/'
>>> urls.build("downloads/show", {'id': 42})
'/downloads/42'