内容简介: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 路由配置
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。