python flask源码解析_Python Web Flask源码解读(二)——路由原理

关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任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方法中有两个参数rule和options。rule是url规则,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方法将rule和option构造成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)' % (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 \

'(?/?)' 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)

environ 是Web服务器传递过来的参数,request_context(environ)会创建一个请求上下文实例,通过预处理preprocess_request之后就会进入分发请求dispatch_request,然后是执行响应make_response和process_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 学习资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值