flask 源码解析3:路由

栏目: Python · 发布时间: 6年前

内容简介:flask 源码解析3:路由

一个 web 应用不同的路径会有不同的处理函数, 路由就是根据请求的 URL 找到对应处理函数的过程。

在执行查找之前,需要有一个规则列表,它存储了 url 和处理函数的对应关系。最容易想到的解决方案就是定义一个字典,key 是 url,value 是对应的处理函数。如果 url 都是静态的(url 路径都是实现确定的,没有变量和正则匹配),那么路由的过程就是从字典中通过 url 这个 key ,找到并返回对应的 value;如果没有找到,就报 404 错误。而对于动态路由,还需要更复杂的匹配逻辑。flask 中的路由过程是这样的吗?这篇文章就来分析分析。

在分析路由匹配过程之前,我们先来看看 flask 中,构建这个路由规则的两种方法:

  1. 通过 @app.route() decorator,比如文章开头给出的 hello world 例子
  2. 通过 app.add_url_rule ,这个方法的签名为 add_url_rule(self, rule, endpoint=None, view_func=None, **options) ,参数的含义如下:
    • rule : url 规则字符串,可以是静态的 /path ,也可以包含 /
    • endpoint :要注册规则的 endpoint,默认是 view_func 的名字
    • view_func :对应 url 的处理函数,也被称为视图函数

这两种方法是等价的,也就是说:

@app.route('/')
def hello():
    return "hello, world!"

也可以写成

def hello():
    return "hello, world!"

app.add_url_rule('/', 'hello', hello)

NOTE: 其实,还有一种方法来构建路由规则——直接操作 app.url_map 这个数据结构。不过这种方法并不是很常用,因此就不展开了。

注册路由规则的时候,flask 内部做了哪些东西呢?我们来看看 route 方法:

def route(self, rule, **options):
    """A decorator that is used to register a view function for a
    given URL rule.  This does the same thing as :meth:`add_url_rule`
    but is intended for decorator usage.
    """

    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f

    return decorator

route 方法内部也是调用 add_url_rule ,只不过在外面包了一层装饰器的逻辑,这也验证了上面两种方法等价的说法。

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    """Connects a URL rule.  Works exactly like the :meth:`route`
    decorator.  If a view_func is provided it will be registered with the
    endpoint.
    """

    methods = options.pop('methods', None)

    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

上面这段代码省略了处理 endpoint 和构建 methods 的部分逻辑,可以看到它主要做的事情就是更新 self.url_mapself.view_functions 两个变量。找到变量的定义,发现 url_mapwerkzeug.routeing:Map 类的对象, rulewerkzeug.routing:Rule 类的对象, view_functions 就是一个字典。这和我们之前预想的并不一样,这里增加了 RuleMap 的封装,还把 urlview_func 保存到了不同的地方。

需要注意的是:每个视图函数的 endpoint 必须是不同的,否则会报 AssertionError

werkzeug 路由逻辑

事实上,flask 核心的路由逻辑是在 werkzeug 中实现的。所以在继续分析之前,我们先看一下 werkzeug 提供的路由功能。

>>> m = Map([
...     Rule('/', endpoint='index'),
...     Rule('/downloads/', endpoint='downloads/index'),
...     Rule('/downloads/<int:id>', endpoint='downloads/show')
... ])
>>> urls = m.bind("example.com", "/")
>>> urls.match("/", "GET")
('index', {})
>>> urls.match("/downloads/42")
('downloads/show', {'id': 42})

>>> urls.match("/downloads")
Traceback (most recent call last):
  ...
RequestRedirect: http://example.com/downloads/
>>> urls.match("/missing")
Traceback (most recent call last):
  ...
NotFound: 404 Not Found

上面的代码演示了 werkzeug 最核心的路由功能:添加路由规则(也可以使用 m.add ),把路由表绑定到特定的环境( m.bind ),匹配url( urls.match )。正常情况下返回对应的 endpoint 名字和参数字典,可能报重定向或者 404 异常。

可以发现, endpoint 在路由过程中非常重要 werkzeug 的路由过程,其实是 url 到 endpoint 的转换:通过 url 找到处理该 url 的 endpoint。至于 endpoint 和 view function 之间的匹配关系, werkzeug 是不管的,而上面也看到 flask 是把这个存放到字典中的。

flask 路由实现

好,有了这些基础知识,我们回头看 dispatch_request ,继续探寻路由匹配的逻辑:

def dispatch_request(self):
    """Does the request dispatching.  Matches the URL and returns the
    return value of the view or error handler.  This does not have to
    be a response object.  In order to convert the return value to a
    proper response object, call :func:`make_response`.
    """

    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule = req.url_rule

    # dispatch to the handler for that endpoint
    return self.view_functions[rule.endpoint](**req.view_args)

这个方法做的事情就是找到请求对象 request ,获取它的 endpoint ,然后从 view_functions 找到对应 endpointview_func ,把请求参数传递过去,进行处理并返回。 view_functions 中的内容,我们已经看到,是在构建路由规则的时候保存进去的;那请求中 req.url_rule 是什么保存进去的呢?它的格式又是什么?

我们可以先这样理解: _request_ctx_stack.top.request 保存着当前请求的信息,在每次请求过来的时候, flask 会把当前请求的信息保存进去,这样我们就能在整个请求处理过程中使用它。至于怎么做到并发情况下信息不会相互干扰错乱,我们将在下一篇文章介绍。

_request_ctx_stack 中保存的是 RequestContext 对象,它出现在 flask/ctx.py 文件中,和路由相关的逻辑如下:

class RequestContext(object):
	def __init__(self, app, environ, request=None):
        self.app = app
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.match_request()

    def match_request(self):
        """Can be overridden by a subclass to hook into the matching
        of the request.
        """
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException as e:
            self.request.routing_exception = e


class Flask(_PackageBoundObject):
    def create_url_adapter(self, request):
        """Creates a URL adapter for the given request.  The URL adapter
        is created at a point where the request context is not yet set up
        so the request is passed explicitly.
        """
        if request is not None:
            return self.url_map.bind_to_environ(request.environ,
                server_name=self.config['SERVER_NAME'])

在初始化的时候,会调用 app.create_url_adapter 方法,把 appurl_map 绑定到 WSGI environ 变量上( bind_to_environ 和之前的 bind 方法作用相同)。最后会调用 match_request 方法,这个方式调用了 url_adapter.match 方法,进行实际的匹配工作,返回匹配的 url rule。而我们之前使用的 url_rule.endpoint 就是匹配的 endpoint 值。

整个 flask 的路由过程就结束了,总结一下大致的流程:

  • 通过 @app.route 或者 app.add_url_rule 注册应用 url 对应的处理函数
  • 每次请求过来的时候,会事先调用路由匹配的逻辑,把路由结果保存起来
  • dispatch_request 根据保存的路由结果,调用对应的视图函数

match 实现

虽然讲完了 flask 的路由流程,但是还没有讲到最核心的问题: werkzeug 中是怎么实现 match 方法的。 Map 保存了 Rule 列表, match 的时候会依次调用其中的 rule.match 方法,如果匹配就找到了 match。 Rule.match 方法的代码如下:

def match(self, path):
        """Check if the rule matches a given path. Path is a string in the
        form ``"subdomain|/path(method)"`` and is assembled by the map.  If
        the map is doing host matching the subdomain part will be the host
        instead.

        If the rule matches a dict with the converted values is returned,
        otherwise the return value is `None`.
        """
        if not self.build_only:
            m = self._regex.search(path)
            if m is not None:
                groups = m.groupdict()

                result = {}
                for name, value in iteritems(groups):
                    try:
                        value = self._converters[name].to_python(value)
                    except ValidationError:
                        return
                    result[str(name)] = value
                if self.defaults:
                    result.update(self.defaults)

                return result

它的逻辑是这样的:用实现 compile 的正则表达式去匹配给出的真实路径信息,把所有的匹配组件转换成对应的值,保存在字典中(这就是传递给视图函数的参数列表)并返回。


以上所述就是小编给大家介绍的《flask 源码解析3:路由》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Machine Learning in Action

Machine Learning in Action

Peter Harrington / Manning Publications / 2012-4-19 / GBP 29.99

It's been said that data is the new "dirt"—the raw material from which and on which you build the structures of the modern world. And like dirt, data can seem like a limitless, undifferentiated mass. ......一起来看看 《Machine Learning in Action》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

html转js在线工具
html转js在线工具

html转js在线工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具