Flask 源码分析完整教程目录:https://www.cnblogs.com/nickchen121/p/14763457.html

一、flask 源码解析:路由

1.1 构建路由规则

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

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

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

  1. 通过 [@app.route](http://cizixs.com/cdn-cgi/l/email-protection#bdddfddccdcd93cfd2c8c9d8)() 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

1.2 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 是把这个存放到字典中的。

1.3 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](http://cizixs.com/cdn-cgi/l/email-protection#c3a383a2b3b3edb1acb6b7a6)或者app.add_url_rule 注册应用 url 对应的处理函数
  • 每次请求过来的时候,会事先调用路由匹配的逻辑,把路由结果保存起来
  • dispatch_request 根据保存的路由结果,调用对应的视图函数

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

3.flask 源码解析:路由的更多相关文章

  1. flask源码解析之上下文为什么用栈

    楔子 我在之前的文章<flask源码解析之上下文>中对flask上下文流程进行了详细的说明,但是在学习的过程中我一直在思考flask上下文中为什么要使用栈完成对请求上下文和应用上下文的入栈 ...

  2. flask源码解析之上下文

    引入 对于flask而言,其请求过程与django有着截然不同的流程.在django中是将请求一步步封装最终传入视图函数的参数中,但是在flask中,视图函数中并没有请求参数,而是将请求通过上下文机制 ...

  3. Flask源码解析:Flask应用执行流程及原理

    WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...

  4. flask源码解析之DispatcherMiddleware

    DispatcherMiddleware作用 实现多app的应用,完成路由分发的功能 如何使用 from werkzeug.wsgi import DispatcherMiddleware from ...

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

    文章出处  https://www.cnblogs.com/jackchengcc/archive/2018/11/29/10025949.html 一:什么是上下文 每一段程序都有很多外部变量.只有 ...

  6. flask源码解析之session

    内容回顾 cookie与session的区别: 1. session 是保存在服务端的键值对 2. cookie 只能保存4096个字节的数据,但是session不受限制 3. cookie保存在浏览 ...

  7. Flask源码解析:Flask上下文

    一.上下文(Context) 什么是上下文: 每一段程序都有很多外部变量.只有像Add这种简单的函数才是没有外部变量的.一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行.你为了使他们运行, ...

  8. 用尽洪荒之力学习Flask源码

    WSGIapp.run()werkzeug@app.route('/')ContextLocalLocalStackLocalProxyContext CreateStack pushStack po ...

  9. Flask源码分析二:路由内部实现原理

    前言 Flask是目前为止我最喜欢的一个Python Web框架了,为了更好的掌握其内部实现机制,这两天准备学习下Flask的源码,将由浅入深跟大家分享下,其中Flask版本为1.1.1. 上次了解了 ...

  10. kubernetes源码解析---- apiserver路由构建解析(1)

    kubernetes源码解析---- apiserver路由构建解析(1) apiserver作为k8s集群的唯一入口,内部主要实现了两个功能,一个是请求的路由和处理,简单说就是监听一个端口,把接收到 ...

随机推荐

  1. 【Mybatis】11 注解的使用

    文档引用:http://www.mybatis.cn/archives/678.html 视频参考:https://www.bilibili.com/video/BV1NE411Q7Nx?p=15 注 ...

  2. 【H5】11 表格

    概述: 在HTML中一个很普通的任务是构建表格数据,有大量的元素和属性是来满足这种需求的. 只需要一点儿的CSS来设定风格,HTML让在web上显示表格数据变的很容易,例如你的学校的教学计划,你当地的 ...

  3. 【转载】 IEEE Signal Processing Letters(SPL)投稿经验分享

    版权声明:本文为CSDN博主「yellow7-」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/weixin_4 ...

  4. 如何关闭NVIDIA显卡的CUDA运算功能

    基本很少有人会想到要关闭NVIDIA显卡的CUDA计算功能,这东西不想用就不跑cuda代码不就可以了吗,但是作为一个深度学习服务器集群的管理者来说,这个功能还是有用处的.前一阵使用实验室的一台服务器的 ...

  5. 2023 ICPC网络赛第一场(A,D,J,L)

    2023 ICPC网络赛第一场(A,D,J,L) A Qualifiers Ranking Rules 先把两场比赛的学校排名处理出来,然后两场比赛的同位次进行合并即可 #include <bi ...

  6. git忽略文件夹中除了指定的文件外的其他所有文件

    **/test/* !**/test/a.py

  7. kafka 运维

    查看kafka topic列表 #集群需要先执行unset JMX_PORT ./kafka-topics.sh --zookeeper gitee-zookeeper:2181 --list 查看t ...

  8. canfestival源码下载教程

    先进入官网的这个页面 https://canfestival.org/code.html.en 点击箭头所指的地方 选择日期最新的点击下载

  9. pip 安装包时提示 "WARNING: Skipping xxx due to invalid metadata entry 'name'"

    我最近在使用 pip 安装包的时候经常遇到如下警告: WARNING: Skipping /opt/homebrew/lib/python3.11/site-packages/numpy-1.26.3 ...

  10. 【Python】Selenium自动化测试之滑块拼图验证码图片方法

    在项目中有时验证码是滑块拼图形式的,这种验证码该如何完成验证呢? 有以下几个步骤: 目录 第一步:得到验证码图片 第二步:匹配缺口照片在完整照片的位置 第三步:机器模拟人工滑动轨迹 第四步:判断拼图是 ...