关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。

Github:https://github.com/hylinux1024

微信公众号:终身开发者(angrycode)

上一篇的话题,继续阅读Flask的源码,来看一下这个框架路由原理

0x00 路由原理

首先看下Flask的简易用法

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
return f'Hello, World!' if __name__ == '__main__':
app.run()

Flask中是使用@app.route这个装饰器来实现url和方法之间的映射的。

Flask.route

打开route方法

def route(self, rule, **options):
"""这个方法的注释非常详细,为了避免代码篇幅过长,这里省略注释"""
def decorator(f):
self.add_url_rule(rule, f.__name__, **options)
self.view_functions[f.__name__] = f
return f return decorator

route方法中有两个参数ruleoptionsruleurl规则,options参数主要是werkzeug.routing.Rule类使用。 方法内部还定义decorator方法,将url路径规则,和方法名称对应关系保存起来,然后将函数方法名与函数对象也对应的保存到一个字典中。

Flask.add_url_rule
def add_url_rule(self, rule, endpoint, **options):
options['endpoint'] = endpoint
options.setdefault('methods', ('GET',))
self.url_map.add(Rule(rule, **options))

这个方法的注释也是很详细的,大概的意思如果定义了一个方法

@app.route('/')
def index():
pass

等价于

def index():
pass
app.add_url_rule('index', '/')
app.view_functions['index'] = index

最后调用url_map.add方法将ruleoption构造成Rule添加到一个Map对象中。

Rule

Rule表示url规则,它是在werkzeug函数库中定义的类。

url_map是一个自定义的Map对象。它的目的就是实现url与方法之间映射关系。

Map.add
def add(self, rulefactory):
"""Add a new rule or factory to the map and bind it. Requires that the
rule is not bound to another map. :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
"""
for rule in rulefactory.get_rules(self):
rule.bind(self)
self._rules.append(rule)
self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
self._remap = True

add方法中就调用了rule中的bind方法,这里才是真正实现绑定的逻辑。

Rule.bind
def bind(self, map, rebind=False):
"""Bind the url to a map and create a regular expression based on
the information from the rule itself and the defaults from the map. :internal:
"""
if self.map is not None and not rebind:
raise RuntimeError('url rule %r already bound to map %r' %
(self, self.map))
# 将url与map对应起来,即将map保存在rule对象自身的map属性上
self.map = map
if self.strict_slashes is None:
self.strict_slashes = map.strict_slashes
if self.subdomain is None:
self.subdomain = map.default_subdomain rule = self.subdomain + '|' + (self.is_leaf and self.rule or self.rule.rstrip('/')) self._trace = []
self._converters = {}
self._weights = [] regex_parts = []
for converter, arguments, variable in parse_rule(rule):
if converter is None:
regex_parts.append(re.escape(variable))
self._trace.append((False, variable))
self._weights.append(len(variable))
else:
convobj = get_converter(map, converter, arguments)
regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
self._converters[variable] = convobj
self._trace.append((True, variable))
self._weights.append(convobj.weight)
self.arguments.add(str(variable))
if convobj.is_greedy:
self.greediness += 1
if not self.is_leaf:
self._trace.append((False, '/')) if not self.build_only:
regex = r'^%s%s$' % (
u''.join(regex_parts),
(not self.is_leaf or not self.strict_slashes) and \
'(?<!/)(?P<__suffix__>/?)' or ''
)
self._regex = re.compile(regex, re.UNICODE)

bind方法中的for循环中调用了parse_url方法,这是一个生成器函数,它使用正则进行并yield回一个元组。这个方法的细节还是挺多的,但这里我们抓住主脉络,先把整体流程搞清楚。

Flask启动时从装饰器route开始就把会把url和响应的函数方法对应起来。

调用逻辑为

Flask.route -> Flask.add_url_rule -> Map.add -> Rule.bind

0x01 响应请求

当服务启动之后,Flask会默认开启一个Web服务器,便于开发调试,而实际环境中可能会使用nginx+gunicorn等工具进行部署。由于部署不是本节主题,我们还是专注于客户端请求是如何响应的。

上一篇我们知道Flask通过Werkzeug函数库中的run_simple方法将服务启动了。

当客户端发送请求时这个方法会被执行

Flask.wsgi_app
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in
`__call__` so that middlewares can be applied: app.wsgi_app = MyMiddleware(app.wsgi_app) :param environ: a WSGI environment
:param start_response: a callable accepting a status code, a list of headers and an optional
exception context to start the response
"""
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)

environWeb服务器传递过来的参数,request_context(environ)会创建一个请求上下文实例,通过预处理preprocess_request之后就会进入分发请求dispatch_request,然后是执行响应make_responseprocess_response,最后返回response

这里我们重点关注dispatch_request

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`.
"""
try:
endpoint, values = self.match_request()
return self.view_functions[endpoint](**values)
except HTTPException as e:
handler = self.error_handlers.get(e.code)
if handler is None:
return e
return handler(e)
except Exception as e:
handler = self.error_handlers.get(500)
if self.debug or handler is None:
raise
return handler(e)

这个方法的核心就是match_request,通过匹配客户端请求的url规则找到对应函数方法。

Flask.match_request
def match_request(self):
"""Matches the current request against the URL map and also
stores the endpoint and view arguments on the request object
is successful, otherwise the exception is stored.
"""
rv = _request_ctx_stack.top.url_adapter.match()
request.endpoint, request.view_args = rv
return rv

匹配完成后就会调用self.view_functions[endpoint](**values)来执行对应函数方法,并返回函数的返回值。

如果上述dispatch_request没有匹配到url规则,则会执行error_handlers字典中找到对应的错误码执行handler方法。

至此url路由规则匹配过程就完成了。

0x02 总结一下

Flask启动后会把route装饰器解析后,把url规则与函数方法进行对应保存。

在客户端请求时,Flask.wsgi_app方法会被执行,并开始匹配url找到对应的方法,执行后将结果返回。

0x03 学习资料

Python Web Flask源码解读(二)——路由原理的更多相关文章

  1. Python Web Flask源码解读(一)——启动流程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  2. Python Web Flask源码解读(三)——模板渲染过程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

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

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

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

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

  5. jQuery.Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  6. (转)go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

    转自:http://www.baiyuxiong.com/?p=886 ---------------------------------------------------------------- ...

  7. Flask源码解读--所有可扩展点

    一.前言 flask中有很多可扩展点(笔者这样称呼),其中包含了信号和请求钩子,这些信号和钩子有什么用呢?其主要作用用于帮助我们进行程序的耦合性,当然还可以让我们自定义一些行为.话不多说,通过阅读源码 ...

  8. mybatis源码解读(二)——构建Configuration对象

    Configuration 对象保存了所有mybatis的配置信息,主要包括: ①. mybatis-configuration.xml 基础配置文件 ②. mapper.xml 映射器配置文件 1. ...

  9. go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

    nsqlookupd: 官方文档解释见:http://bitly.github.io/nsq/components/nsqlookupd.html 用官方话来讲是:nsqlookupd管理拓扑信息,客 ...

随机推荐

  1. PhpCms V9中的{date('Y-m-d',$r[inputtime])}问题解决方法

    不少朋友会碰到这个问题:在PhpCms V9中的首页或者文章内容页调用发布时间{date('Y-m-d',$r[inputtime])}调用显示1970-01-01,然后尝试用截断的方法也没有成功,应 ...

  2. mysql查看视图用户

    select `DEFINER` from  information_schema.VIEWS;

  3. spark 源码分析之二十一 -- Task的执行流程

    引言 在上两篇文章 spark 源码分析之十九 -- DAG的生成和Stage的划分 和 spark 源码分析之二十 -- Stage的提交 中剖析了Spark的DAG的生成,Stage的划分以及St ...

  4. Hadoop自学系列集(二) ---- CentOS下安装JDK

    上篇我们讲述了如何使用VMware安装CentOS系统,接下来就看如何安装我们最为熟悉的jdk吧!安装前先看看系统上有没有安装过jdk,输入java -version,如果查询出了其他版本的jdk版本 ...

  5. 基于zookeeper集群的云平台-配置中心的功能设计

    最近准备找工作面试,就研究了下基于zookeeper集群的配置中心. 下面是自己设想的关于开源的基于zookeeper集群的云平台-配置中心的功能设计.大家觉得哪里有问题,请提出宝贵的意见和建议,谢谢 ...

  6. Java几种常见的排序算法

    一.所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法.排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面. ...

  7. Socket编程:UDP和TCP概论及案例

    网络编程的三要素: 1.IP地址  2.端口 3.协议 什么是Socket? Socket就是通信链路的端点称"套接词". 基于TCP协议的Socket网络通信: 用来实现双向安全 ...

  8. 利用Idea重构功能及Java8语法特性——优化深层嵌套代码

    当遇到深层嵌套代码,如for,if,lambda表达式或内部类及这些代码的组合,这时我们可以通过Java 8的语法特性来进行优化. 下面的代码是一个嵌套循环的示例. public MappedFiel ...

  9. Nginx + Lua 搭建网站WAF防火墙

    前言 对于项目里面只是使用代理等常用功能,在线安装即可,如需制定化模块,则推荐编译安装 PS:本文不仅仅包含Nginx相关的知识点,还包含了逆天学习方法(对待新事物的处理) 官方网站:https:// ...

  10. 分布式ID系列(3)——数据库自增ID机制适合做分布式ID吗

    数据库自增ID机制原理介绍 在分布式里面,数据库的自增ID机制的主要原理是:数据库自增ID和mysql数据库的replace_into()函数实现的.这里的replace数据库自增ID和mysql数据 ...