关于我
编程界的一名小小程序猿,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是咱们团队的主要技术栈。
联系:hylinux1024@gmail.com
微信公众号:angrycodehtml
接上一篇的话题,继续阅读Flask
的源码,来看一下这个框架路由原理。python
首先看下Flask
的简易用法linux
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return f'Hello, World!'
if __name__ == '__main__':
app.run()
复制代码
在Flask
中是使用@app.route
这个装饰器来实现url
和方法之间的映射的。nginx
打开route
方法express
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
路径规则,和方法名称对应关系保存起来,而后将函数方法名与函数对象也对应的保存到一个字典中。编程
def add_url_rule(self, rule, endpoint, **options):
options['endpoint'] = endpoint
options.setdefault('methods', ('GET',))
self.url_map.add(Rule(rule, **options))
复制代码
这个方法的注释也是很详细的,大概的意思若是定义了一个方法flask
@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
对象中。bash
Rule
表示url
规则,它是在werkzeug
函数库中定义的类。服务器
url_map
是一个自定义的Map
对象。它的目的就是实现url
与方法之间映射关系。
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
方法,这里才是真正实现绑定的逻辑。
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
复制代码
当服务启动以后,Flask
会默认开启一个Web
服务器,便于开发调试,而实际环境中可能会使用nginx+gunicorn
等工具进行部署。因为部署不是本节主题,咱们仍是专一于客户端请求是如何响应的。
在上一篇咱们知道Flask
经过Werkzeug
函数库中的run_simple
方法将服务启动了。
当客户端发送请求时这个方法会被执行
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
。
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
规则找到对应函数方法。
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
路由规则匹配过程就完成了。
在Flask
启动后会把route
装饰器解析后,把url
规则与函数方法进行对应保存。 在客户端请求时,Flask.wsgi_app
方法会被执行,并开始匹配url
找到对应的方法,执行后将结果返回。