flask源码解读04: Route(路由)

  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__>/?)$,读者可以根据得到的结果理解前面生成正则表达式的过程。

 

转载于:https://www.cnblogs.com/lovelaker007/p/8570208.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值