Flask源码复习之路由

构建路由规则

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

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

在分析路由匹配过程之前,我们先来看看 flask 中,构建这个路由规则的两种方法:
1 .通过 @app.route() decorator,比如文章开头给出的 hello world 例子

  1. 通过 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 的处理函数,也被称为视图函数
    两种方法等价
# 1.装饰器
@app.route('/')
    def index():
        return 'Hello World‘

# 2.add_url_rule(rule, endpoint, f, **options)
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::
            @app.route('/')
            def index():
                return 'Hello World‘
        """
        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_map 是 werkzeug.routeing:Map 类的对象,rule 是werkzeug.routing:Rule 类的对象,view_functions 就是一个字典。这和我们之前预想的并不一样,这里增加了 Rule 和 Map 的封装,还把 url 和 view_func 保存到了不同的地方。

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

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 方法,把 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 的正则表达式去匹配给出的真实路径信息,把所有的匹配组件转换成对应的值,保存在字典中(这就是传递给视图函数的参数列表)并返回。

原文地址:https://www.cnblogs.com/donghaoblogs/p/10605278.html

时间: 2024-10-27 05:27:23

Flask源码复习之路由的相关文章

Flask源码解读之路由部分

从django的rest framwork过渡到flask框架的时候,经常会想flask的路由部分是怎么走的,这篇博客将一步步展示从启动程序到请求来路径和函数是怎么去匹配的. 1.首先是启动flask程序,python解释器就会从上到下加载我们的app @app.route('/home',endpoint='index') def home(): return render_template('dist/index.html') 2.在函数上的装饰器即会运行,我们进入装饰器中查看运行内容 de

Flask源码解析(理解working outside of application context)

from flask import Flask, current_app app = Flask(__name__) a = current_app d = current_app.config['DEBUG'] 首先从这段代码看起,代码运行的结果就是 RuntimeError: Working outside of application context. 此时本地代理未绑定,不是我们想要的核心flask对象.代码报错. current_app = LocalProxy(_find_app)

11.6 Flask 源码流程,上下文管理

源码流程 创建对象 from flask import Flask """ 1 实例化对象 app """ app = Flask(__name__) """ 2 设置路由 将路由关系放在 app.url_map = {} 中 """ @app.route("/index") def index(): return "index" if —__na

flask 源码解析:上下文(一)

文章出处  https://www.cnblogs.com/jackchengcc/archive/2018/11/29/10025949.html 一:什么是上下文 每一段程序都有很多外部变量.只有像Add这种简单的函数才是没有外部变量的.一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行.你为了使他们运行,就要给所有的外部变量一个一个写一些值进去.这些值的集合就叫上下文. 在 flask 中,视图函数需要知道它执行情况的请求信息(请求的 url,参数,方法等)以及应用信息(应用中初

Flask 源码阅读笔记

我觉得我已经养成了一个坏习惯,在使用一个框架过程中对它的内部原理非常感兴趣,有时候需要花不少精力才 明白,这也导致了学习的缓慢,但换来的是对框架的内部机理的熟悉,正如侯捷所说,源码面前,了无秘密.这也是 本文产生的直接原因. 一.flask session原理 flask的session是通过客户端的cookie实现的,不同于diango的服务器端实现,flask通过itsdangerous这个苦 将session的内容序列化到浏览器的cookie,当浏览器再次请求时将反序列化cookie内容,

Flask源码阅读-第二篇(flask\__init__.py)

源码: # -*- coding: utf-8 -*-""" flask ~~~~~ A microframework based on Werkzeug. It's extensively documented and follows best practice patterns. :copyright: ? 2010 by the Pallets team. :license: BSD, see LICENSE for more details.""&

Python Web Flask源码解读(四)——全局变量

关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https://github.com/hylinux1024 微信公众号:终身开发者(angrycode) Flask中全局变量有current_app.request.g和session.不过需要注意的是虽然标题是写着全局变量,但实际上这些变量都跟当前请求的上下文环境有关,下面一起来看看. current_ap

flask 源码浅析(flask 如何处理请求(多线程,多进程,IO多路复用))

2018-04-04 13:09:47 lucky404 阅读数 5724更多 分类专栏: python 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/lucky404/article/details/79815997 之前有阅读过tornado 底层的实现,tornado 为了解决C10K 问题(没听说过C10K问题的请查看: http://www.360doc.com/content

Flask源码分析一:服务启动

前言 Flask是目前为止我最喜欢的一个Python Web框架了,为了更好的掌握其内部实现机制,这两天准备学习下Flask的源码,将由浅入深跟大家分享下,其中Flask版本为1.1.1. Flask系列文章: Flask开发初探 正文 本文将结合源码跟踪看下Flask是如何启动并运行一个服务的. 首先,继续贴上最简单的应用: from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): retu