Route(路由)的功能是提供url和视图函数之间的匹配。当我们在浏览器中输入url,app获得该url后,需要找到与之对应的视图函数来生成响应。这种对应关系就由Route记录。
下面是定义Route的两个示例
@app.route('/register', methods=['GET', 'POST']) def register(): pass @auth.route('/login/<int:id>', methods=['GET', 'POST']) def login(): pass
第一个route关联了'/register'和register函数, 第二个route关联了'/login/<int:id>'和login函数。第一个route是静态路由, url是一个不会发生改变的字符串;第二个路由是动态路由, <int:id>说明这部分会发生改变,且必须能正确的转换成整数。当不同的用户login时,提供不同的id,这样可以针对不同的用户进行登陆检测
Route的定义
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): if not string.startswith('/'): raise ValueError('urls must start with a leading slash') self.rule = string self.is_leaf = not string.endswith('/') self.map = None self.strict_slashes = strict_slashes self.subdomain = subdomain self.host = host self.defaults = defaults self.build_only = build_only self.alias = alias 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') self.endpoint = endpoint self.redirect_to = redirect_to if defaults: self.arguments = set(map(str, defaults)) else: self.arguments = set() self._trace = self._converters = self._regex = self._weights = None
__init__方法比较简单,是一些属性绑定,我们常见的就是绑定self.rule和self.endpoint
下面对route进行解析
def compile(self): """Compiles the regular expression and stores it.""" assert self.map is not None, 'rule not bound' if self.map.host_matching: domain_rule = self.host or '' else: domain_rule = self.subdomain or '' self._trace = [] self._converters = {} self._weights = [] regex_parts = [] def _build_regex(rule): for converter, arguments, variable in parse_rule(rule): if converter is None: regex_parts.append(re.escape(variable)) self._trace.append((False, variable)) for part in variable.split('/'): if part: self._weights.append((0, -len(part))) else: if arguments: c_args, c_kwargs = parse_converter_args(arguments) else: c_args = () c_kwargs = {} convobj = self.get_converter( variable, converter, c_args, c_kwargs) regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex)) self._converters[variable] = convobj self._trace.append((True, variable)) self._weights.append((1, convobj.weight)) self.arguments.add(str(variable)) _build_regex(domain_rule) regex_parts.append('\\|') self._trace.append((False, '|')) _build_regex(self.is_leaf and self.rule or self.rule.rstrip('/')) if not self.is_leaf: self._trace.append((False, '/')) if self.build_only: return regex = r'^%s%s$' % ( u''.join(regex_parts), (not self.is_leaf or not self.strict_slashes) and '(?<!/)(?P<__suffix__>/?)' or '' ) self._regex = re.compile(regex, re.UNICODE)
compile方法的作用就是将路由解析成一个正则表达式,无论这个路由是静态的还是动态的。当用户输入动态url时,对应的正则表达式还负责从url中提取出动态部分表示的参数
compile方法嵌套定义了_build_regex函数,_build_regex函数的第一条语句for converter, arguments, variable in parse_rule(rule):说明该函数调用了parse_rule解析rule。
def parse_rule(rule): """Parse a rule and return it as generator. Each iteration yields tuples in the form ``(converter, arguments, variable)``. If the converter is `None` it's a static url part, otherwise it's a dynamic one. :internal: """ pos = 0 end = len(rule) do_match = _rule_re.match used_names = set() while pos < end: m = do_match(rule, pos) if m is None: break data = m.groupdict() if data['static']: yield None, None, data['static'] variable = data['variable'] converter = data['converter'] or 'default' if variable in used_names: raise ValueError('variable name %r used twice.' % variable) used_names.add(variable) yield converter, data['args'] or None, variable pos = m.end() if pos < end: remaining = rule[pos:] if '>' in remaining or '<' in remaining: raise ValueError('malformed url rule: %r' % rule) yield None, None, remaining
parse_rule函数中,do_match = _rule_re.match,do_match是_rule_re(正则表达式)的match方法,_rule_re定义如下:
_rule_re = re.compile(r''' (?P<static>[^<]*) # static rule data < (?: (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # converter name (?:\((?P<args>.*?)\))? # converter arguments \: # variable delimiter )? (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*) # variable name > ''', re.VERBOSE)
(?P<static>[^<]*)是命名分组,匹配不包含'<'任意字符,这表示rule中的静态部分,和我们前面的示例对应,动态静态的区分点是'<'字符。(?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)表示动态部分的转换器,(?:\((?P<args>.*?)\))? 表示转换器参数,注意该参数是被'()'括起来的,(?P<variable>[a-zA-Z_][a-zA-Z0-9_]*)是动态部分的名称。对应到前面的实例,int是转换器,id是名称,没有定义转换器参数。
回到parse_rule函数中,在while pos < end:循环中,不断用正则对rule匹配。如果结果中有static分组就向外送出None, None, data['static'],接着向外送出converter, data['args'] or None, variable,这三个数据指明了rule中的一个动态部分。匹配完动态部分后,yield None, None, remaining向外送出剩下的部分,这部分里面不能包含'<'或者'>',否则报错。
再回到rule.compile方法,for converter, arguments, variable in parse_rule(rule): 语句不断从rule的解析中获取三个参数,对于静态部分,converter, arguments为None,regex_parts.append(re.escape(variable))和self._trace.append((False, variable))两条语句说明向两个列表中添加静态部分的原样即可。
对于动态部分regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex)) self._converters[variable] = convobj self._trace.append((True, variable))说明添加了命名分组,convobj.regex表示由转换器生成的convobj对象对应的正则表达式,比如float的正则表达式就是r'\d+\.\d+'。
下面是一个示例
from werkzeug.routing import Rule, Map r = Rule('/browse/<int:id>/', endpoint='kb/browse') m = Map([r]) print r._regex.pattern
运行上面这段代码, 得到的结果是^\|\/browse\/(?P<id>\d+)(?<!/)(?P<__suffix__>/?)$,读者可以根据得到的结果理解前面生成正则表达式的过程。