内容简介:flask 源码解析3:路由
一个 web 应用不同的路径会有不同的处理函数, 路由就是根据请求的 URL 找到对应处理函数的过程。
在执行查找之前,需要有一个规则列表,它存储了 url 和处理函数的对应关系。最容易想到的解决方案就是定义一个字典,key 是 url,value 是对应的处理函数。如果 url 都是静态的(url 路径都是实现确定的,没有变量和正则匹配),那么路由的过程就是从字典中通过 url 这个 key ,找到并返回对应的 value;如果没有找到,就报 404 错误。而对于动态路由,还需要更复杂的匹配逻辑。flask 中的路由过程是这样的吗?这篇文章就来分析分析。
在分析路由匹配过程之前,我们先来看看 flask
中,构建这个路由规则的两种方法:
-
通过
@app.route()
decorator,比如文章开头给出的 hello world 例子 -
通过
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_map
和 self.view_functions
两个变量。找到变量的定义,发现 url_map
是 werkzeug.routeing:Map
类的对象, rule
是 werkzeug.routing:Rule
类的对象, view_functions
就是一个字典。这和我们之前预想的并不一样,这里增加了 Rule
和 Map
的封装,还把 url
和 view_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
找到对应 endpoint
的 view_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
方法,把 app
的 url_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:路由》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- gin 源码阅读(二)-- 路由和路由组
- express源码分析-路由
- RocketMQ源码分析之路由中心
- Laravel HTTP——添加路由源码分析
- RocketMQ 源码分析之路由中心(NameServer)
- 网关 Spring-Cloud-Gateway 源码解析 —— 路由(2.2)之 RouteDefinitionRouteLocator 路由配置
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
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》 这本书的介绍吧!